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 }