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  /** 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}