001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one
003     * or more contributor license agreements.  See the NOTICE file
004     * distributed with this work for additional information
005     * regarding copyright ownership.  The ASF licenses this file
006     * to you under the Apache License, Version 2.0 (the
007     * "License"); you may not use this file except in compliance
008     * with the License.  You may obtain a copy of the License at
009     *
010     *     http://www.apache.org/licenses/LICENSE-2.0
011     *
012     * Unless required by applicable law or agreed to in writing, software
013     * distributed under the License is distributed on an "AS IS" BASIS,
014     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015     * See the License for the specific language governing permissions and
016     * limitations under the License.
017     */
018    package org.apache.hadoop.fs.permission;
019    
020    import java.io.DataInput;
021    import java.io.DataOutput;
022    import java.io.IOException;
023    
024    import org.apache.commons.logging.Log;
025    import org.apache.commons.logging.LogFactory;
026    import org.apache.hadoop.classification.InterfaceAudience;
027    import org.apache.hadoop.classification.InterfaceStability;
028    import org.apache.hadoop.conf.Configuration;
029    import org.apache.hadoop.fs.CommonConfigurationKeys;
030    import org.apache.hadoop.io.Writable;
031    import org.apache.hadoop.io.WritableFactories;
032    import org.apache.hadoop.io.WritableFactory;
033    
034    /**
035     * A class for file/directory permissions.
036     */
037    @InterfaceAudience.Public
038    @InterfaceStability.Stable
039    public class FsPermission implements Writable {
040      private static final Log LOG = LogFactory.getLog(FsPermission.class);
041    
042      static final WritableFactory FACTORY = new WritableFactory() {
043        @Override
044        public Writable newInstance() { return new FsPermission(); }
045      };
046      static {                                      // register a ctor
047        WritableFactories.setFactory(FsPermission.class, FACTORY);
048        WritableFactories.setFactory(ImmutableFsPermission.class, FACTORY);
049      }
050    
051      /** Create an immutable {@link FsPermission} object. */
052      public static FsPermission createImmutable(short permission) {
053        return new ImmutableFsPermission(permission);
054      }
055    
056      //POSIX permission style
057      private FsAction useraction = null;
058      private FsAction groupaction = null;
059      private FsAction otheraction = null;
060      private boolean stickyBit = false;
061    
062      private FsPermission() {}
063    
064      /**
065       * Construct by the given {@link FsAction}.
066       * @param u user action
067       * @param g group action
068       * @param o other action
069       */
070      public FsPermission(FsAction u, FsAction g, FsAction o) {
071        this(u, g, o, false);
072      }
073    
074      public FsPermission(FsAction u, FsAction g, FsAction o, boolean sb) {
075        set(u, g, o, sb);
076      }
077    
078      /**
079       * Construct by the given mode.
080       * @param mode
081       * @see #toShort()
082       */
083      public FsPermission(short mode) { fromShort(mode); }
084    
085      /**
086       * Copy constructor
087       * 
088       * @param other other permission
089       */
090      public FsPermission(FsPermission other) {
091        this.useraction = other.useraction;
092        this.groupaction = other.groupaction;
093        this.otheraction = other.otheraction;
094        this.stickyBit = other.stickyBit;
095      }
096      
097      /**
098       * Construct by given mode, either in octal or symbolic format.
099       * @param mode mode as a string, either in octal or symbolic format
100       * @throws IllegalArgumentException if <code>mode</code> is invalid
101       */
102      public FsPermission(String mode) {
103        this(new UmaskParser(mode).getUMask());
104      }
105    
106      /** Return user {@link FsAction}. */
107      public FsAction getUserAction() {return useraction;}
108    
109      /** Return group {@link FsAction}. */
110      public FsAction getGroupAction() {return groupaction;}
111    
112      /** Return other {@link FsAction}. */
113      public FsAction getOtherAction() {return otheraction;}
114    
115      private void set(FsAction u, FsAction g, FsAction o, boolean sb) {
116        useraction = u;
117        groupaction = g;
118        otheraction = o;
119        stickyBit = sb;
120      }
121    
122      public void fromShort(short n) {
123        FsAction[] v = FsAction.values();
124    
125        set(v[(n >>> 6) & 7], v[(n >>> 3) & 7], v[n & 7], (((n >>> 9) & 1) == 1) );
126      }
127    
128      @Override
129      public void write(DataOutput out) throws IOException {
130        out.writeShort(toShort());
131      }
132    
133      @Override
134      public void readFields(DataInput in) throws IOException {
135        fromShort(in.readShort());
136      }
137    
138      /**
139       * Create and initialize a {@link FsPermission} from {@link DataInput}.
140       */
141      public static FsPermission read(DataInput in) throws IOException {
142        FsPermission p = new FsPermission();
143        p.readFields(in);
144        return p;
145      }
146    
147      /**
148       * Encode the object to a short.
149       */
150      public short toShort() {
151        int s =  (stickyBit ? 1 << 9 : 0)     |
152                 (useraction.ordinal() << 6)  |
153                 (groupaction.ordinal() << 3) |
154                 otheraction.ordinal();
155    
156        return (short)s;
157      }
158    
159      @Override
160      public boolean equals(Object obj) {
161        if (obj instanceof FsPermission) {
162          FsPermission that = (FsPermission)obj;
163          return this.useraction == that.useraction
164              && this.groupaction == that.groupaction
165              && this.otheraction == that.otheraction
166              && this.stickyBit == that.stickyBit;
167        }
168        return false;
169      }
170    
171      @Override
172      public int hashCode() {return toShort();}
173    
174      @Override
175      public String toString() {
176        String str = useraction.SYMBOL + groupaction.SYMBOL + otheraction.SYMBOL;
177        if(stickyBit) {
178          StringBuilder str2 = new StringBuilder(str);
179          str2.replace(str2.length() - 1, str2.length(),
180               otheraction.implies(FsAction.EXECUTE) ? "t" : "T");
181          str = str2.toString();
182        }
183    
184        return str;
185      }
186    
187      /**
188       * Apply a umask to this permission and return a new one.
189       *
190       * The umask is used by create, mkdir, and other Hadoop filesystem operations.
191       * The mode argument for these operations is modified by removing the bits
192       * which are set in the umask.  Thus, the umask limits the permissions which
193       * newly created files and directories get.
194       *
195       * @param umask              The umask to use
196       * 
197       * @return                   The effective permission
198       */
199      public FsPermission applyUMask(FsPermission umask) {
200        return new FsPermission(useraction.and(umask.useraction.not()),
201            groupaction.and(umask.groupaction.not()),
202            otheraction.and(umask.otheraction.not()));
203      }
204    
205      /** umask property label deprecated key and code in getUMask method
206       *  to accommodate it may be removed in version .23 */
207      public static final String DEPRECATED_UMASK_LABEL = "dfs.umask"; 
208      public static final String UMASK_LABEL = 
209                      CommonConfigurationKeys.FS_PERMISSIONS_UMASK_KEY;
210      public static final int DEFAULT_UMASK = 
211                      CommonConfigurationKeys.FS_PERMISSIONS_UMASK_DEFAULT;
212    
213      /** 
214       * Get the user file creation mask (umask)
215       * 
216       * {@code UMASK_LABEL} config param has umask value that is either symbolic 
217       * or octal.
218       * 
219       * Symbolic umask is applied relative to file mode creation mask; 
220       * the permission op characters '+' clears the corresponding bit in the mask, 
221       * '-' sets bits in the mask.
222       * 
223       * Octal umask, the specified bits are set in the file mode creation mask.
224       * 
225       * {@code DEPRECATED_UMASK_LABEL} config param has umask value set to decimal.
226       */
227      public static FsPermission getUMask(Configuration conf) {
228        int umask = DEFAULT_UMASK;
229        
230        // To ensure backward compatibility first use the deprecated key.
231        // If the deprecated key is not present then check for the new key
232        if(conf != null) {
233          String confUmask = conf.get(UMASK_LABEL);
234          int oldUmask = conf.getInt(DEPRECATED_UMASK_LABEL, Integer.MIN_VALUE);
235          try {
236            if(confUmask != null) {
237              umask = new UmaskParser(confUmask).getUMask();
238            }
239          } catch(IllegalArgumentException iae) {
240            // Provide more explanation for user-facing message
241            String type = iae instanceof NumberFormatException ? "decimal"
242                : "octal or symbolic";
243            String error = "Unable to parse configuration " + UMASK_LABEL
244                + " with value " + confUmask + " as " + type + " umask.";
245            LOG.warn(error);
246            
247            // If oldUmask is not set, then throw the exception
248            if (oldUmask == Integer.MIN_VALUE) {
249              throw new IllegalArgumentException(error);
250            }
251          }
252            
253          if(oldUmask != Integer.MIN_VALUE) { // Property was set with old key
254            if (umask != oldUmask) {
255              LOG.warn(DEPRECATED_UMASK_LABEL
256                  + " configuration key is deprecated. " + "Convert to "
257                  + UMASK_LABEL + ", using octal or symbolic umask "
258                  + "specifications.");
259              // Old and new umask values do not match - Use old umask
260              umask = oldUmask;
261            }
262          }
263        }
264        
265        return new FsPermission((short)umask);
266      }
267    
268      public boolean getStickyBit() {
269        return stickyBit;
270      }
271    
272      /** Set the user file creation mask (umask) */
273      public static void setUMask(Configuration conf, FsPermission umask) {
274        conf.set(UMASK_LABEL, String.format("%1$03o", umask.toShort()));
275        conf.setInt(DEPRECATED_UMASK_LABEL, umask.toShort());
276      }
277    
278      /**
279       * Get the default permission for directory and symlink.
280       * In previous versions, this default permission was also used to
281       * create files, so files created end up with ugo+x permission.
282       * See HADOOP-9155 for detail. 
283       * Two new methods are added to solve this, please use 
284       * {@link FsPermission#getDirDefault()} for directory, and use
285       * {@link FsPermission#getFileDefault()} for file.
286       * This method is kept for compatibility.
287       */
288      public static FsPermission getDefault() {
289        return new FsPermission((short)00777);
290      }
291    
292      /**
293       * Get the default permission for directory.
294       */
295      public static FsPermission getDirDefault() {
296        return new FsPermission((short)00777);
297      }
298    
299      /**
300       * Get the default permission for file.
301       */
302      public static FsPermission getFileDefault() {
303        return new FsPermission((short)00666);
304      }
305    
306      /**
307       * Create a FsPermission from a Unix symbolic permission string
308       * @param unixSymbolicPermission e.g. "-rw-rw-rw-"
309       */
310      public static FsPermission valueOf(String unixSymbolicPermission) {
311        if (unixSymbolicPermission == null) {
312          return null;
313        }
314        else if (unixSymbolicPermission.length() != 10) {
315          throw new IllegalArgumentException("length != 10(unixSymbolicPermission="
316              + unixSymbolicPermission + ")");
317        }
318    
319        int n = 0;
320        for(int i = 1; i < unixSymbolicPermission.length(); i++) {
321          n = n << 1;
322          char c = unixSymbolicPermission.charAt(i);
323          n += (c == '-' || c == 'T' || c == 'S') ? 0: 1;
324        }
325    
326        // Add sticky bit value if set
327        if(unixSymbolicPermission.charAt(9) == 't' ||
328            unixSymbolicPermission.charAt(9) == 'T')
329          n += 01000;
330    
331        return new FsPermission((short)n);
332      }
333      
334      private static class ImmutableFsPermission extends FsPermission {
335        public ImmutableFsPermission(short permission) {
336          super(permission);
337        }
338        @Override
339        public FsPermission applyUMask(FsPermission umask) {
340          throw new UnsupportedOperationException();
341        }
342        @Override
343        public void readFields(DataInput in) throws IOException {
344          throw new UnsupportedOperationException();
345        }    
346      }
347    }