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        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      /**
187       * Apply a umask to this permission and return a new one.
188       *
189       * The umask is used by create, mkdir, and other Hadoop filesystem operations.
190       * The mode argument for these operations is modified by removing the bits
191       * which are set in the umask.  Thus, the umask limits the permissions which
192       * newly created files and directories get.
193       *
194       * @param umask              The umask to use
195       * 
196       * @return                   The effective permission
197       */
198      public FsPermission applyUMask(FsPermission umask) {
199        return new FsPermission(useraction.and(umask.useraction.not()),
200            groupaction.and(umask.groupaction.not()),
201            otheraction.and(umask.otheraction.not()));
202      }
203    
204      /** umask property label deprecated key and code in getUMask method
205       *  to accommodate it may be removed in version .23 */
206      public static final String DEPRECATED_UMASK_LABEL = "dfs.umask"; 
207      public static final String UMASK_LABEL = 
208                      CommonConfigurationKeys.FS_PERMISSIONS_UMASK_KEY;
209      public static final int DEFAULT_UMASK = 
210                      CommonConfigurationKeys.FS_PERMISSIONS_UMASK_DEFAULT;
211    
212      /** 
213       * Get the user file creation mask (umask)
214       * 
215       * {@code UMASK_LABEL} config param has umask value that is either symbolic 
216       * or octal.
217       * 
218       * Symbolic umask is applied relative to file mode creation mask; 
219       * the permission op characters '+' clears the corresponding bit in the mask, 
220       * '-' sets bits in the mask.
221       * 
222       * Octal umask, the specified bits are set in the file mode creation mask.
223       * 
224       * {@code DEPRECATED_UMASK_LABEL} config param has umask value set to decimal.
225       */
226      public static FsPermission getUMask(Configuration conf) {
227        int umask = DEFAULT_UMASK;
228        
229        // To ensure backward compatibility first use the deprecated key.
230        // If the deprecated key is not present then check for the new key
231        if(conf != null) {
232          String confUmask = conf.get(UMASK_LABEL);
233          int oldUmask = conf.getInt(DEPRECATED_UMASK_LABEL, Integer.MIN_VALUE);
234          try {
235            if(confUmask != null) {
236              umask = new UmaskParser(confUmask).getUMask();
237            }
238          } catch(IllegalArgumentException iae) {
239            // Provide more explanation for user-facing message
240            String type = iae instanceof NumberFormatException ? "decimal"
241                : "octal or symbolic";
242            String error = "Unable to parse configuration " + UMASK_LABEL
243                + " with value " + confUmask + " as " + type + " umask.";
244            LOG.warn(error);
245            
246            // If oldUmask is not set, then throw the exception
247            if (oldUmask == Integer.MIN_VALUE) {
248              throw new IllegalArgumentException(error);
249            }
250          }
251            
252          if(oldUmask != Integer.MIN_VALUE) { // Property was set with old key
253            if (umask != oldUmask) {
254              LOG.warn(DEPRECATED_UMASK_LABEL
255                  + " configuration key is deprecated. " + "Convert to "
256                  + UMASK_LABEL + ", using octal or symbolic umask "
257                  + "specifications.");
258              // Old and new umask values do not match - Use old umask
259              umask = oldUmask;
260            }
261          }
262        }
263        
264        return new FsPermission((short)umask);
265      }
266    
267      public boolean getStickyBit() {
268        return stickyBit;
269      }
270    
271      /** Set the user file creation mask (umask) */
272      public static void setUMask(Configuration conf, FsPermission umask) {
273        conf.set(UMASK_LABEL, String.format("%1$03o", umask.toShort()));
274        conf.setInt(DEPRECATED_UMASK_LABEL, umask.toShort());
275      }
276    
277      /**
278       * Get the default permission for directory and symlink.
279       * In previous versions, this default permission was also used to
280       * create files, so files created end up with ugo+x permission.
281       * See HADOOP-9155 for detail. 
282       * Two new methods are added to solve this, please use 
283       * {@link FsPermission#getDirDefault()} for directory, and use
284       * {@link FsPermission#getFileDefault()} for file.
285       * This method is kept for compatibility.
286       */
287      public static FsPermission getDefault() {
288        return new FsPermission((short)00777);
289      }
290    
291      /**
292       * Get the default permission for directory.
293       */
294      public static FsPermission getDirDefault() {
295        return new FsPermission((short)00777);
296      }
297    
298      /**
299       * Get the default permission for file.
300       */
301      public static FsPermission getFileDefault() {
302        return new FsPermission((short)00666);
303      }
304    
305      /**
306       * Create a FsPermission from a Unix symbolic permission string
307       * @param unixSymbolicPermission e.g. "-rw-rw-rw-"
308       */
309      public static FsPermission valueOf(String unixSymbolicPermission) {
310        if (unixSymbolicPermission == null) {
311          return null;
312        }
313        else if (unixSymbolicPermission.length() != 10) {
314          throw new IllegalArgumentException("length != 10(unixSymbolicPermission="
315              + unixSymbolicPermission + ")");
316        }
317    
318        int n = 0;
319        for(int i = 1; i < unixSymbolicPermission.length(); i++) {
320          n = n << 1;
321          char c = unixSymbolicPermission.charAt(i);
322          n += (c == '-' || c == 'T' || c == 'S') ? 0: 1;
323        }
324    
325        // Add sticky bit value if set
326        if(unixSymbolicPermission.charAt(9) == 't' ||
327            unixSymbolicPermission.charAt(9) == 'T')
328          n += 01000;
329    
330        return new FsPermission((short)n);
331      }
332      
333      private static class ImmutableFsPermission extends FsPermission {
334        public ImmutableFsPermission(short permission) {
335          super(permission);
336        }
337        public FsPermission applyUMask(FsPermission umask) {
338          throw new UnsupportedOperationException();
339        }
340        public void readFields(DataInput in) throws IOException {
341          throw new UnsupportedOperationException();
342        }    
343      }
344    }