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
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  /**
298   * Returns true if the file is encrypted or directory is in an encryption zone
299   */
300  public boolean getEncryptedBit() {
301    return false;
302  }
303
304  /** Set the user file creation mask (umask) */
305  public static void setUMask(Configuration conf, FsPermission umask) {
306    conf.set(UMASK_LABEL, String.format("%1$03o", umask.toShort()));
307    conf.setInt(DEPRECATED_UMASK_LABEL, umask.toShort());
308  }
309
310  /**
311   * Get the default permission for directory and symlink.
312   * In previous versions, this default permission was also used to
313   * create files, so files created end up with ugo+x permission.
314   * See HADOOP-9155 for detail. 
315   * Two new methods are added to solve this, please use 
316   * {@link FsPermission#getDirDefault()} for directory, and use
317   * {@link FsPermission#getFileDefault()} for file.
318   * This method is kept for compatibility.
319   */
320  public static FsPermission getDefault() {
321    return new FsPermission((short)00777);
322  }
323
324  /**
325   * Get the default permission for directory.
326   */
327  public static FsPermission getDirDefault() {
328    return new FsPermission((short)00777);
329  }
330
331  /**
332   * Get the default permission for file.
333   */
334  public static FsPermission getFileDefault() {
335    return new FsPermission((short)00666);
336  }
337
338  /**
339   * Get the default permission for cache pools.
340   */
341  public static FsPermission getCachePoolDefault() {
342    return new FsPermission((short)00755);
343  }
344
345  /**
346   * Create a FsPermission from a Unix symbolic permission string
347   * @param unixSymbolicPermission e.g. "-rw-rw-rw-"
348   */
349  public static FsPermission valueOf(String unixSymbolicPermission) {
350    if (unixSymbolicPermission == null) {
351      return null;
352    }
353    else if (unixSymbolicPermission.length() != MAX_PERMISSION_LENGTH) {
354      throw new IllegalArgumentException(String.format(
355        "length != %d(unixSymbolicPermission=%s)", MAX_PERMISSION_LENGTH,
356        unixSymbolicPermission));
357    }
358
359    int n = 0;
360    for(int i = 1; i < unixSymbolicPermission.length(); i++) {
361      n = n << 1;
362      char c = unixSymbolicPermission.charAt(i);
363      n += (c == '-' || c == 'T' || c == 'S') ? 0: 1;
364    }
365
366    // Add sticky bit value if set
367    if(unixSymbolicPermission.charAt(9) == 't' ||
368        unixSymbolicPermission.charAt(9) == 'T')
369      n += 01000;
370
371    return new FsPermission((short)n);
372  }
373  
374  private static class ImmutableFsPermission extends FsPermission {
375    public ImmutableFsPermission(short permission) {
376      super(permission);
377    }
378    @Override
379    public FsPermission applyUMask(FsPermission umask) {
380      throw new UnsupportedOperationException();
381    }
382    @Override
383    public void readFields(DataInput in) throws IOException {
384      throw new UnsupportedOperationException();
385    }    
386  }
387}