/** * 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; import java.io.IOException; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.hadoop.fs.FsShell.CmdHandler; import org.apache.hadoop.fs.permission.FsPermission; /** * This class is the home for file permissions related commands. * Moved to this seperate class since FsShell is getting too large. */ class FsShellPermissions { /*========== chmod ==========*/ /* The pattern is alsmost as flexible as mode allowed by * chmod shell command. The main restriction is that we recognize only rwxX. * To reduce errors we also enforce 3 digits for octal mode. */ private static Pattern chmodNormalPattern = Pattern.compile("\\G\\s*([ugoa]*)([+=-]+)([rwxX]+)([,\\s]*)\\s*"); private static Pattern chmodOctalPattern = Pattern.compile("^\\s*[+]?([0-7]{3})\\s*$"); static String CHMOD_USAGE = "-chmod [-R] <MODE[,MODE]... | OCTALMODE> PATH..."; private static class ChmodHandler extends CmdHandler { private short userMode, groupMode, othersMode; private char userType = '+', groupType = '+', othersType='+'; private void applyNormalPattern(String modeStr, Matcher matcher) throws IOException { 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 : [rwxX]+ * 4 : [,\s]* */ String str = matcher.group(2); char type = str.charAt(str.length() - 1); boolean user, group, others; user = group = others = 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; default : throw new RuntimeException("Unexpected"); } } if ( user ) { userMode = mode; userType = type; } if ( group ) { groupMode = mode; groupType = type; } if ( others ) { othersMode = mode; othersType = type; } commaSeperated = matcher.group(4).contains(","); } } private void applyOctalPattern(String modeStr, Matcher matcher) { userType = groupType = othersType = '='; String str = matcher.group(1); 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); } } 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; } @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(userType, userMode, (existing>>>6)&7, exeOk) << 6 | applyChmod(groupType, groupMode, (existing>>>3)&7, exeOk) << 3 | applyChmod(othersType, othersMode, existing&7, exeOk) ); if (existing != newperms) { try { srcFs.setPermission(file.getPath(), new FsPermission((short)newperms)); } catch (IOException e) { System.err.println(getName() + ": changing permissions of '" + file.getPath() + "':" + e.getMessage().split("\n")[0]); } } } } /*========== chown ==========*/ static private String allowedChars = "[-_./@a-zA-Z0-9]"; ///allows only "allowedChars" above in names for owner and group static private Pattern chownPattern = Pattern.compile("^\\s*(" + allowedChars + "+)?" + "([:](" + allowedChars + "*))?\\s*$"); static private Pattern chgrpPattern = Pattern.compile("^\\s*(" + allowedChars + "+)\\s*$"); static String CHOWN_USAGE = "-chown [-R] [OWNER][:[GROUP]] PATH..."; static String CHGRP_USAGE = "-chgrp [-R] GROUP PATH..."; private static class ChownHandler extends CmdHandler { protected String owner = null; protected String group = null; protected ChownHandler(String cmd, FileSystem fs) { //for chgrp super(cmd, fs); } ChownHandler(FileSystem fs, String ownerStr) throws IOException { super("chown", fs); Matcher matcher = chownPattern.matcher(ownerStr); if (!matcher.matches()) { throw new IOException("'" + ownerStr + "' does not match " + "expected pattern for [owner][:group]."); } owner = matcher.group(1); group = matcher.group(3); if (group != null && group.length() == 0) { group = null; } if (owner == null && group == null) { throw new IOException("'" + ownerStr + "' does not specify " + " owner or group."); } } @Override public void run(FileStatus file, FileSystem srcFs) throws IOException { //Should we do case insensitive match? String newOwner = (owner == null || owner.equals(file.getOwner())) ? null : owner; String newGroup = (group == null || group.equals(file.getGroup())) ? null : group; if (newOwner != null || newGroup != null) { try { srcFs.setOwner(file.getPath(), newOwner, newGroup); } catch (IOException e) { System.err.println(getName() + ": changing ownership of '" + file.getPath() + "':" + e.getMessage().split("\n")[0]); } } } } /*========== chgrp ==========*/ private static class ChgrpHandler extends ChownHandler { ChgrpHandler(FileSystem fs, String groupStr) throws IOException { super("chgrp", fs); Matcher matcher = chgrpPattern.matcher(groupStr); if (!matcher.matches()) { throw new IOException("'" + groupStr + "' does not match " + "expected pattern for group"); } group = matcher.group(1); } } static void changePermissions(FileSystem fs, String cmd, String argv[], int startIndex, FsShell shell) throws IOException { CmdHandler handler = null; boolean recursive = false; // handle common arguments, currently only "-R" for (; startIndex < argv.length && argv[startIndex].equals("-R"); startIndex++) { recursive = true; } if ( startIndex >= argv.length ) { throw new IOException("Not enough arguments for the command"); } if (cmd.equals("-chmod")) { handler = new ChmodHandler(fs, argv[startIndex++]); } else if (cmd.equals("-chown")) { handler = new ChownHandler(fs, argv[startIndex++]); } else if (cmd.equals("-chgrp")) { handler = new ChgrpHandler(fs, argv[startIndex++]); } shell.runCmdHandler(handler, argv, startIndex, recursive); } }