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