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