diff --git a/CHANGES.txt b/CHANGES.txt index bd78c0096a..b208d69dfc 100644 --- a/CHANGES.txt +++ b/CHANGES.txt @@ -181,6 +181,10 @@ Trunk (unreleased changes) HADOOP-6235. Adds new method to FileSystem for clients to get server defaults. (Kan Zhang via suresh) + HADOOP-6234. Add new option dfs.umaskmode to set umask in configuration + to use octal or symbolic instead of decimal. (Jakob Homan via suresh) + + IMPROVEMENTS HADOOP-4565. Added CombineFileInputFormat to use data locality information diff --git a/src/docs/src/documentation/content/xdocs/hdfs_permissions_guide.xml b/src/docs/src/documentation/content/xdocs/hdfs_permissions_guide.xml index 946b34229b..6b11b20a63 100644 --- a/src/docs/src/documentation/content/xdocs/hdfs_permissions_guide.xml +++ b/src/docs/src/documentation/content/xdocs/hdfs_permissions_guide.xml @@ -178,9 +178,9 @@ If a cluster starts with a version 0.15 data set (fsimage), all fil
The choice of initial mode during upgrade. The x permission is never set for files. For configuration files, the decimal value 51110 may be used.
-
dfs.umask = 022
+
dfs.umaskmode = 022
- The umask used when creating files and directories. For configuration files, the decimal value 1810 may be used. + The umask used when creating files and directories. May be specified either via three octal digits or symbolic values, with the same constraints as the dfs chmod command.
diff --git a/src/java/org/apache/hadoop/fs/FsShellPermissions.java b/src/java/org/apache/hadoop/fs/FsShellPermissions.java index 27997c7e7a..7d1a69e942 100644 --- a/src/java/org/apache/hadoop/fs/FsShellPermissions.java +++ b/src/java/org/apache/hadoop/fs/FsShellPermissions.java @@ -23,6 +23,7 @@ import org.apache.hadoop.fs.FsShell.CmdHandler; import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.fs.permission.ChmodParser; /** @@ -39,168 +40,33 @@ class FsShellPermissions { * also enforce octal mode specifications of either 3 digits without a sticky * bit setting or four digits with a sticky bit setting. */ - private static Pattern chmodNormalPattern = - Pattern.compile("\\G\\s*([ugoa]*)([+=-]+)([rwxXt]+)([,\\s]*)\\s*"); - private static Pattern chmodOctalPattern = - Pattern.compile("^\\s*[+]?([01]?)([0-7]{3})\\s*$"); static String CHMOD_USAGE = "-chmod [-R] PATH..."; + private static ChmodParser pp; + private static class ChmodHandler extends CmdHandler { - private short userMode; - private short groupMode; - private short othersMode; - private short stickyMode; - private char userType = '+'; - private char groupType = '+'; - private char othersType = '+'; - private char stickyBitType = '+'; - - private void applyNormalPattern(String modeStr, Matcher matcher) - throws IOException { - // Are there multiple permissions stored in one chmod? - boolean commaSeperated = false; - - for(int i=0; i < 1 || matcher.end() < modeStr.length(); i++) { - if (i>0 && (!commaSeperated || !matcher.find())) { - patternError(modeStr); - } - - /* groups : 1 : [ugoa]* - * 2 : [+-=] - * 3 : [rwxXt]+ - * 4 : [,\s]* - */ - - String str = matcher.group(2); - char type = str.charAt(str.length() - 1); - - boolean user, group, others, stickyBit; - user = group = others = stickyBit = false; - - for(char c : matcher.group(1).toCharArray()) { - switch (c) { - case 'u' : user = true; break; - case 'g' : group = true; break; - case 'o' : others = true; break; - case 'a' : break; - default : throw new RuntimeException("Unexpected"); - } - } - - if (!(user || group || others)) { // same as specifying 'a' - user = group = others = true; - } - - short mode = 0; - - for(char c : matcher.group(3).toCharArray()) { - switch (c) { - case 'r' : mode |= 4; break; - case 'w' : mode |= 2; break; - case 'x' : mode |= 1; break; - case 'X' : mode |= 8; break; - case 't' : stickyBit = true; break; - default : throw new RuntimeException("Unexpected"); - } - } - - if ( user ) { - userMode = mode; - userType = type; - } - - if ( group ) { - groupMode = mode; - groupType = type; - } - - if ( others ) { - othersMode = mode; - othersType = type; - - stickyMode = (short) (stickyBit ? 1 : 0); - stickyBitType = type; - } - - commaSeperated = matcher.group(4).contains(","); - } - } - - private void applyOctalPattern(String modeStr, Matcher matcher) { - userType = groupType = othersType = '='; - - // Check if sticky bit is specified - String sb = matcher.group(1); - if(!sb.isEmpty()) { - stickyMode = Short.valueOf(sb.substring(0, 1)); - stickyBitType = '='; - } - - String str = matcher.group(2); - userMode = Short.valueOf(str.substring(0, 1)); - groupMode = Short.valueOf(str.substring(1, 2)); - othersMode = Short.valueOf(str.substring(2, 3)); - } - - private void patternError(String mode) throws IOException { - throw new IOException("chmod : mode '" + mode + - "' does not match the expected pattern."); - } - ChmodHandler(FileSystem fs, String modeStr) throws IOException { super("chmod", fs); - Matcher matcher = null; - - if ((matcher = chmodNormalPattern.matcher(modeStr)).find()) { - applyNormalPattern(modeStr, matcher); - } else if ((matcher = chmodOctalPattern.matcher(modeStr)).matches()) { - applyOctalPattern(modeStr, matcher); - } else { - patternError(modeStr); + try { + pp = new ChmodParser(modeStr); + } catch(IllegalArgumentException iea) { + patternError(iea.getMessage()); } } - private int applyChmod(char type, int mode, int existing, boolean exeOk) { - boolean capX = false; - - if ((mode&8) != 0) { // convert X to x; - capX = true; - mode &= ~8; - mode |= 1; - } - - switch (type) { - case '+' : mode = mode | existing; break; - case '-' : mode = (~mode) & existing; break; - case '=' : break; - default : throw new RuntimeException("Unexpected"); - } - - // if X is specified add 'x' only if exeOk or x was already set. - if (capX && !exeOk && (mode&1) != 0 && (existing&1) == 0) { - mode &= ~1; // remove x - } - - return mode; + private void patternError(String mode) throws IOException { + throw new IOException("chmod : mode '" + mode + + "' does not match the expected pattern."); } - + @Override public void run(FileStatus file, FileSystem srcFs) throws IOException { - FsPermission perms = file.getPermission(); - int existing = perms.toShort(); - boolean exeOk = file.isDir() || (existing & 0111) != 0; - int newperms = ( applyChmod(stickyBitType, stickyMode, - (existing>>>9), false) << 9 | - applyChmod(userType, userMode, - (existing>>>6)&7, exeOk) << 6 | - applyChmod(groupType, groupMode, - (existing>>>3)&7, exeOk) << 3 | - applyChmod(othersType, othersMode, existing&7, exeOk)); + int newperms = pp.applyNewPermission(file); - if (existing != newperms) { + if (file.getPermission().toShort() != newperms) { try { srcFs.setPermission(file.getPath(), new FsPermission((short)newperms)); diff --git a/src/java/org/apache/hadoop/fs/permission/ChmodParser.java b/src/java/org/apache/hadoop/fs/permission/ChmodParser.java new file mode 100644 index 0000000000..f088ef24b4 --- /dev/null +++ b/src/java/org/apache/hadoop/fs/permission/ChmodParser.java @@ -0,0 +1,51 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.fs.permission; + +import java.util.regex.Pattern; + +import org.apache.hadoop.fs.FileStatus; + +/** + * Parse a permission mode passed in from a chmod command and apply that + * mode against an existing file. + */ +public class ChmodParser extends PermissionParser { + private static Pattern chmodOctalPattern = + Pattern.compile("^\\s*[+]?([01]?)([0-7]{3})\\s*$"); + private static Pattern chmodNormalPattern = + Pattern.compile("\\G\\s*([ugoa]*)([+=-]+)([rwxXt]+)([,\\s]*)\\s*"); + + public ChmodParser(String modeStr) throws IllegalArgumentException { + super(modeStr, chmodNormalPattern, chmodOctalPattern); + } + + /** + * Apply permission against specified file and determine what the + * new mode would be + * @param file File against which to apply mode + * @return File's new mode if applied. + */ + public short applyNewPermission(FileStatus file) { + FsPermission perms = file.getPermission(); + int existing = perms.toShort(); + boolean exeOk = file.isDir() || (existing & 0111) != 0; + + return (short)combineModes(existing, exeOk); + } +} diff --git a/src/java/org/apache/hadoop/fs/permission/FsPermission.java b/src/java/org/apache/hadoop/fs/permission/FsPermission.java index e92d35bcea..e5acf5b88b 100644 --- a/src/java/org/apache/hadoop/fs/permission/FsPermission.java +++ b/src/java/org/apache/hadoop/fs/permission/FsPermission.java @@ -17,17 +17,23 @@ */ package org.apache.hadoop.fs.permission; -import org.apache.hadoop.conf.Configuration; -import org.apache.hadoop.io.*; - import java.io.DataInput; import java.io.DataOutput; import java.io.IOException; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.hadoop.conf.Configuration; +import org.apache.hadoop.io.Writable; +import org.apache.hadoop.io.WritableFactories; +import org.apache.hadoop.io.WritableFactory; + /** * A class for file/directory permissions. */ public class FsPermission implements Writable { + private static final Log LOG = LogFactory.getLog(FsPermission.class); + static final WritableFactory FACTORY = new WritableFactory() { public Writable newInstance() { return new FsPermission(); } }; @@ -176,15 +182,31 @@ public FsPermission applyUMask(FsPermission umask) { } /** umask property label */ - public static final String UMASK_LABEL = "dfs.umask"; + public static final String DEPRECATED_UMASK_LABEL = "dfs.umask"; + public static final String UMASK_LABEL = "dfs.umaskmode"; public static final int DEFAULT_UMASK = 0022; /** Get the user file creation mask (umask) */ public static FsPermission getUMask(Configuration conf) { int umask = DEFAULT_UMASK; - if (conf != null) { - umask = conf.getInt(UMASK_LABEL, DEFAULT_UMASK); + + // Attempt to pull value from configuration, trying new key first and then + // deprecated key, along with a warning, if not present + if(conf != null) { + String confUmask = conf.get(UMASK_LABEL); + if(confUmask != null) { // UMASK_LABEL is set + umask = new UmaskParser(confUmask).getUMask(); + } else { // check for deprecated key label + int oldStyleValue = conf.getInt(DEPRECATED_UMASK_LABEL, Integer.MIN_VALUE); + if(oldStyleValue != Integer.MIN_VALUE) { // Property was set with old key + LOG.warn(DEPRECATED_UMASK_LABEL + " configuration key is deprecated. " + + "Convert to " + UMASK_LABEL + ", using octal or symbolic umask " + + "specifications."); + umask = oldStyleValue; + } + } } + return new FsPermission((short)umask); } diff --git a/src/java/org/apache/hadoop/fs/permission/PermissionParser.java b/src/java/org/apache/hadoop/fs/permission/PermissionParser.java new file mode 100644 index 0000000000..e8fae43fa6 --- /dev/null +++ b/src/java/org/apache/hadoop/fs/permission/PermissionParser.java @@ -0,0 +1,195 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.fs.permission; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Base class for parsing either chmod permissions or umask permissions. + * Includes common code needed by either operation as implemented in + * UmaskParser and ChmodParser classes. + */ +class PermissionParser { + protected short userMode; + protected short groupMode; + protected short othersMode; + protected short stickyMode; + protected char userType = '+'; + protected char groupType = '+'; + protected char othersType = '+'; + protected char stickyBitType = '+'; + + /** + * Begin parsing permission stored in modeStr + * + * @param modeStr Permission mode, either octal or symbolic + * @param symbolic Use-case specific symbolic pattern to match against + * @throws IllegalArgumentException if unable to parse modeStr + */ + public PermissionParser(String modeStr, Pattern symbolic, Pattern octal) + throws IllegalArgumentException { + Matcher matcher = null; + + if ((matcher = symbolic.matcher(modeStr)).find()) { + applyNormalPattern(modeStr, matcher); + } else if ((matcher = octal.matcher(modeStr)).matches()) { + applyOctalPattern(modeStr, matcher); + } else { + throw new IllegalArgumentException(modeStr); + } + } + + private void applyNormalPattern(String modeStr, Matcher matcher) { + // Are there multiple permissions stored in one chmod? + boolean commaSeperated = false; + + for (int i = 0; i < 1 || matcher.end() < modeStr.length(); i++) { + if (i > 0 && (!commaSeperated || !matcher.find())) { + throw new IllegalArgumentException(modeStr); + } + + /* + * groups : 1 : [ugoa]* 2 : [+-=] 3 : [rwxXt]+ 4 : [,\s]* + */ + + String str = matcher.group(2); + char type = str.charAt(str.length() - 1); + + boolean user, group, others, stickyBit; + user = group = others = stickyBit = false; + + for (char c : matcher.group(1).toCharArray()) { + switch (c) { + case 'u': + user = true; + break; + case 'g': + group = true; + break; + case 'o': + others = true; + break; + case 'a': + break; + default: + throw new RuntimeException("Unexpected"); + } + } + + if (!(user || group || others)) { // same as specifying 'a' + user = group = others = true; + } + + short mode = 0; + + for (char c : matcher.group(3).toCharArray()) { + switch (c) { + case 'r': + mode |= 4; + break; + case 'w': + mode |= 2; + break; + case 'x': + mode |= 1; + break; + case 'X': + mode |= 8; + break; + case 't': + stickyBit = true; + break; + default: + throw new RuntimeException("Unexpected"); + } + } + + if (user) { + userMode = mode; + userType = type; + } + + if (group) { + groupMode = mode; + groupType = type; + } + + if (others) { + othersMode = mode; + othersType = type; + + stickyMode = (short) (stickyBit ? 1 : 0); + stickyBitType = type; + } + + commaSeperated = matcher.group(4).contains(","); + } + } + + private void applyOctalPattern(String modeStr, Matcher matcher) { + userType = groupType = othersType = '='; + + // Check if sticky bit is specified + String sb = matcher.group(1); + if (!sb.isEmpty()) { + stickyMode = Short.valueOf(sb.substring(0, 1)); + stickyBitType = '='; + } + + String str = matcher.group(2); + userMode = Short.valueOf(str.substring(0, 1)); + groupMode = Short.valueOf(str.substring(1, 2)); + othersMode = Short.valueOf(str.substring(2, 3)); + } + + protected int combineModes(int existing, boolean exeOk) { + return combineModeSegments(stickyBitType, stickyMode, + (existing>>>9), false) << 9 | + combineModeSegments(userType, userMode, + (existing>>>6)&7, exeOk) << 6 | + combineModeSegments(groupType, groupMode, + (existing>>>3)&7, exeOk) << 3 | + combineModeSegments(othersType, othersMode, existing&7, exeOk); + } + + protected int combineModeSegments(char type, int mode, + int existing, boolean exeOk) { + boolean capX = false; + + if ((mode&8) != 0) { // convert X to x; + capX = true; + mode &= ~8; + mode |= 1; + } + + switch (type) { + case '+' : mode = mode | existing; break; + case '-' : mode = (~mode) & existing; break; + case '=' : break; + default : throw new RuntimeException("Unexpected"); + } + + // if X is specified add 'x' only if exeOk or x was already set. + if (capX && !exeOk && (mode&1) != 0 && (existing&1) == 0) { + mode &= ~1; // remove x + } + + return mode; + } +} diff --git a/src/java/org/apache/hadoop/fs/permission/UmaskParser.java b/src/java/org/apache/hadoop/fs/permission/UmaskParser.java new file mode 100644 index 0000000000..56bfd678cc --- /dev/null +++ b/src/java/org/apache/hadoop/fs/permission/UmaskParser.java @@ -0,0 +1,45 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.hadoop.fs.permission; + +import java.util.regex.Pattern; + +/** + * Parse umask value provided as a string, either in octal or symbolic + * format and return it as a short value. Umask values are slightly + * different from standard modes as they cannot specify sticky bit + * or X. + * + */ +class UmaskParser extends PermissionParser { + private static Pattern chmodOctalPattern = + Pattern.compile("^\\s*[+]?()([0-7]{3})\\s*$"); // no leading 1 for sticky bit + private static Pattern umaskSymbolicPattern = /* not allow X or t */ + Pattern.compile("\\G\\s*([ugoa]*)([+=-]+)([rwx]+)([,\\s]*)\\s*"); + final short umaskMode; + + public UmaskParser(String modeStr) throws IllegalArgumentException { + super(modeStr, umaskSymbolicPattern, chmodOctalPattern); + + umaskMode = (short)combineModes(0, false); + } + + public short getUMask() { + return umaskMode; + } +} diff --git a/src/test/core/org/apache/hadoop/fs/permission/TestFsPermission.java b/src/test/core/org/apache/hadoop/fs/permission/TestFsPermission.java index 89e9f7d1e7..7d00cb8f38 100644 --- a/src/test/core/org/apache/hadoop/fs/permission/TestFsPermission.java +++ b/src/test/core/org/apache/hadoop/fs/permission/TestFsPermission.java @@ -17,6 +17,10 @@ */ package org.apache.hadoop.fs.permission; +import java.io.IOException; + +import org.apache.hadoop.conf.Configuration; + import junit.framework.TestCase; import static org.apache.hadoop.fs.permission.FsAction.*; @@ -91,7 +95,6 @@ else if(!f.getStickyBit() && f.getOtherAction().implies(EXECUTE)) } public void testFsPermission() { - String symbolic = "-rwxrwxrwx"; StringBuilder b = new StringBuilder("-123456789"); @@ -113,4 +116,49 @@ public void testFsPermission() { } } + public void testUMaskParser() throws IOException { + Configuration conf = new Configuration(); + + // Ensure that we get the right octal values back for all legal values + for(FsAction u : FsAction.values()) { + for(FsAction g : FsAction.values()) { + for(FsAction o : FsAction.values()) { + FsPermission f = new FsPermission(u, g, o); + String asOctal = String.format("%1$03o", f.toShort()); + conf.set(FsPermission.UMASK_LABEL, asOctal); + FsPermission fromConf = FsPermission.getUMask(conf); + assertEquals(f, fromConf); + } + } + } + } + + public void TestSymbolicUmasks() { + Configuration conf = new Configuration(); + + // Test some symbolic settings Setting Octal result + String [] symbolic = new String [] { "a+rw", "666", + "u=x,g=r,o=w", "142", + "u=x", "100" }; + + for(int i = 0; i < symbolic.length; i += 2) { + conf.set(FsPermission.UMASK_LABEL, symbolic[i]); + short val = Short.valueOf(symbolic[i + 1], 8); + assertEquals(val, FsPermission.getUMask(conf).toShort()); + } + } + + public void testBadUmasks() { + Configuration conf = new Configuration(); + + for(String b : new String [] {"1777", "22", "99", "foo", ""}) { + conf.set(FsPermission.UMASK_LABEL, b); + try { + FsPermission.getUMask(conf); + fail("Shouldn't have been able to parse bad umask"); + } catch(IllegalArgumentException iae) { + assertEquals(iae.getMessage(), b); + } + } + } }