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