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      /** Maximum acceptable length of a permission string to parse */
052      public static final int MAX_PERMISSION_LENGTH = 10;
053    
054      /** Create an immutable {@link FsPermission} object. */
055      public static FsPermission createImmutable(short permission) {
056        return new ImmutableFsPermission(permission);
057      }
058    
059      //POSIX permission style
060      private FsAction useraction = null;
061      private FsAction groupaction = null;
062      private FsAction otheraction = null;
063      private boolean stickyBit = false;
064    
065      private FsPermission() {}
066    
067      /**
068       * Construct by the given {@link FsAction}.
069       * @param u user action
070       * @param g group action
071       * @param o other action
072       */
073      public FsPermission(FsAction u, FsAction g, FsAction o) {
074        this(u, g, o, false);
075      }
076    
077      public FsPermission(FsAction u, FsAction g, FsAction o, boolean sb) {
078        set(u, g, o, sb);
079      }
080    
081      /**
082       * Construct by the given mode.
083       * @param mode
084       * @see #toShort()
085       */
086      public FsPermission(short mode) { fromShort(mode); }
087    
088      /**
089       * Copy constructor
090       * 
091       * @param other other permission
092       */
093      public FsPermission(FsPermission other) {
094        this.useraction = other.useraction;
095        this.groupaction = other.groupaction;
096        this.otheraction = other.otheraction;
097        this.stickyBit = other.stickyBit;
098      }
099      
100      /**
101       * Construct by given mode, either in octal or symbolic format.
102       * @param mode mode as a string, either in octal or symbolic format
103       * @throws IllegalArgumentException if <code>mode</code> is invalid
104       */
105      public FsPermission(String mode) {
106        this(new UmaskParser(mode).getUMask());
107      }
108    
109      /** Return user {@link FsAction}. */
110      public FsAction getUserAction() {return useraction;}
111    
112      /** Return group {@link FsAction}. */
113      public FsAction getGroupAction() {return groupaction;}
114    
115      /** Return other {@link FsAction}. */
116      public FsAction getOtherAction() {return otheraction;}
117    
118      private void set(FsAction u, FsAction g, FsAction o, boolean sb) {
119        useraction = u;
120        groupaction = g;
121        otheraction = o;
122        stickyBit = sb;
123      }
124    
125      public void fromShort(short n) {
126        FsAction[] v = FSACTION_VALUES;
127        set(v[(n >>> 6) & 7], v[(n >>> 3) & 7], v[n & 7], (((n >>> 9) & 1) == 1) );
128      }
129    
130      @Override
131      public void write(DataOutput out) throws IOException {
132        out.writeShort(toShort());
133      }
134    
135      @Override
136      public void readFields(DataInput in) throws IOException {
137        fromShort(in.readShort());
138      }
139    
140      /**
141       * Create and initialize a {@link FsPermission} from {@link DataInput}.
142       */
143      public static FsPermission read(DataInput in) throws IOException {
144        FsPermission p = new FsPermission();
145        p.readFields(in);
146        return p;
147      }
148    
149      /**
150       * Encode the object to a short.
151       */
152      public short toShort() {
153        int s =  (stickyBit ? 1 << 9 : 0)     |
154                 (useraction.ordinal() << 6)  |
155                 (groupaction.ordinal() << 3) |
156                 otheraction.ordinal();
157    
158        return (short)s;
159      }
160    
161      /**
162       * Encodes the object to a short.  Unlike {@link #toShort()}, this method may
163       * return values outside the fixed range 00000 - 01777 if extended features
164       * are encoded into this permission, such as the ACL bit.
165       *
166       * @return short extended short representation of this permission
167       */
168      public short toExtendedShort() {
169        return toShort();
170      }
171    
172      @Override
173      public boolean equals(Object obj) {
174        if (obj instanceof FsPermission) {
175          FsPermission that = (FsPermission)obj;
176          return this.useraction == that.useraction
177              && this.groupaction == that.groupaction
178              && this.otheraction == that.otheraction
179              && this.stickyBit == that.stickyBit;
180        }
181        return false;
182      }
183    
184      @Override
185      public int hashCode() {return toShort();}
186    
187      @Override
188      public String toString() {
189        String str = useraction.SYMBOL + groupaction.SYMBOL + otheraction.SYMBOL;
190        if(stickyBit) {
191          StringBuilder str2 = new StringBuilder(str);
192          str2.replace(str2.length() - 1, str2.length(),
193               otheraction.implies(FsAction.EXECUTE) ? "t" : "T");
194          str = str2.toString();
195        }
196    
197        return str;
198      }
199    
200      /**
201       * Apply a umask to this permission and return a new one.
202       *
203       * The umask is used by create, mkdir, and other Hadoop filesystem operations.
204       * The mode argument for these operations is modified by removing the bits
205       * which are set in the umask.  Thus, the umask limits the permissions which
206       * newly created files and directories get.
207       *
208       * @param umask              The umask to use
209       * 
210       * @return                   The effective permission
211       */
212      public FsPermission applyUMask(FsPermission umask) {
213        return new FsPermission(useraction.and(umask.useraction.not()),
214            groupaction.and(umask.groupaction.not()),
215            otheraction.and(umask.otheraction.not()));
216      }
217    
218      /** umask property label deprecated key and code in getUMask method
219       *  to accommodate it may be removed in version .23 */
220      public static final String DEPRECATED_UMASK_LABEL = "dfs.umask"; 
221      public static final String UMASK_LABEL = 
222                      CommonConfigurationKeys.FS_PERMISSIONS_UMASK_KEY;
223      public static final int DEFAULT_UMASK = 
224                      CommonConfigurationKeys.FS_PERMISSIONS_UMASK_DEFAULT;
225    
226      private static final FsAction[] FSACTION_VALUES = FsAction.values();
227    
228      /** 
229       * Get the user file creation mask (umask)
230       * 
231       * {@code UMASK_LABEL} config param has umask value that is either symbolic 
232       * or octal.
233       * 
234       * Symbolic umask is applied relative to file mode creation mask; 
235       * the permission op characters '+' clears the corresponding bit in the mask, 
236       * '-' sets bits in the mask.
237       * 
238       * Octal umask, the specified bits are set in the file mode creation mask.
239       * 
240       * {@code DEPRECATED_UMASK_LABEL} config param has umask value set to decimal.
241       */
242      public static FsPermission getUMask(Configuration conf) {
243        int umask = DEFAULT_UMASK;
244        
245        // To ensure backward compatibility first use the deprecated key.
246        // If the deprecated key is not present then check for the new key
247        if(conf != null) {
248          String confUmask = conf.get(UMASK_LABEL);
249          int oldUmask = conf.getInt(DEPRECATED_UMASK_LABEL, Integer.MIN_VALUE);
250          try {
251            if(confUmask != null) {
252              umask = new UmaskParser(confUmask).getUMask();
253            }
254          } catch(IllegalArgumentException iae) {
255            // Provide more explanation for user-facing message
256            String type = iae instanceof NumberFormatException ? "decimal"
257                : "octal or symbolic";
258            String error = "Unable to parse configuration " + UMASK_LABEL
259                + " with value " + confUmask + " as " + type + " umask.";
260            LOG.warn(error);
261            
262            // If oldUmask is not set, then throw the exception
263            if (oldUmask == Integer.MIN_VALUE) {
264              throw new IllegalArgumentException(error);
265            }
266          }
267            
268          if(oldUmask != Integer.MIN_VALUE) { // Property was set with old key
269            if (umask != oldUmask) {
270              LOG.warn(DEPRECATED_UMASK_LABEL
271                  + " configuration key is deprecated. " + "Convert to "
272                  + UMASK_LABEL + ", using octal or symbolic umask "
273                  + "specifications.");
274              // Old and new umask values do not match - Use old umask
275              umask = oldUmask;
276            }
277          }
278        }
279        
280        return new FsPermission((short)umask);
281      }
282    
283      public boolean getStickyBit() {
284        return stickyBit;
285      }
286    
287      /**
288       * Returns true if there is also an ACL (access control list).
289       *
290       * @return boolean true if there is also an ACL (access control list).
291       */
292      public boolean getAclBit() {
293        // File system subclasses that support the ACL bit would override this.
294        return false;
295      }
296    
297      /** Set the user file creation mask (umask) */
298      public static void setUMask(Configuration conf, FsPermission umask) {
299        conf.set(UMASK_LABEL, String.format("%1$03o", umask.toShort()));
300        conf.setInt(DEPRECATED_UMASK_LABEL, umask.toShort());
301      }
302    
303      /**
304       * Get the default permission for directory and symlink.
305       * In previous versions, this default permission was also used to
306       * create files, so files created end up with ugo+x permission.
307       * See HADOOP-9155 for detail. 
308       * Two new methods are added to solve this, please use 
309       * {@link FsPermission#getDirDefault()} for directory, and use
310       * {@link FsPermission#getFileDefault()} for file.
311       * This method is kept for compatibility.
312       */
313      public static FsPermission getDefault() {
314        return new FsPermission((short)00777);
315      }
316    
317      /**
318       * Get the default permission for directory.
319       */
320      public static FsPermission getDirDefault() {
321        return new FsPermission((short)00777);
322      }
323    
324      /**
325       * Get the default permission for file.
326       */
327      public static FsPermission getFileDefault() {
328        return new FsPermission((short)00666);
329      }
330    
331      /**
332       * Get the default permission for cache pools.
333       */
334      public static FsPermission getCachePoolDefault() {
335        return new FsPermission((short)00755);
336      }
337    
338      /**
339       * Create a FsPermission from a Unix symbolic permission string
340       * @param unixSymbolicPermission e.g. "-rw-rw-rw-"
341       */
342      public static FsPermission valueOf(String unixSymbolicPermission) {
343        if (unixSymbolicPermission == null) {
344          return null;
345        }
346        else if (unixSymbolicPermission.length() != MAX_PERMISSION_LENGTH) {
347          throw new IllegalArgumentException(String.format(
348            "length != %d(unixSymbolicPermission=%s)", MAX_PERMISSION_LENGTH,
349            unixSymbolicPermission));
350        }
351    
352        int n = 0;
353        for(int i = 1; i < unixSymbolicPermission.length(); i++) {
354          n = n << 1;
355          char c = unixSymbolicPermission.charAt(i);
356          n += (c == '-' || c == 'T' || c == 'S') ? 0: 1;
357        }
358    
359        // Add sticky bit value if set
360        if(unixSymbolicPermission.charAt(9) == 't' ||
361            unixSymbolicPermission.charAt(9) == 'T')
362          n += 01000;
363    
364        return new FsPermission((short)n);
365      }
366      
367      private static class ImmutableFsPermission extends FsPermission {
368        public ImmutableFsPermission(short permission) {
369          super(permission);
370        }
371        @Override
372        public FsPermission applyUMask(FsPermission umask) {
373          throw new UnsupportedOperationException();
374        }
375        @Override
376        public void readFields(DataInput in) throws IOException {
377          throw new UnsupportedOperationException();
378        }    
379      }
380    }