| /** |
| * 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.LinkedList; |
| import java.util.regex.Matcher; |
| import java.util.regex.Pattern; |
| |
| import org.apache.commons.logging.Log; |
| import org.apache.hadoop.classification.InterfaceAudience; |
| import org.apache.hadoop.classification.InterfaceStability; |
| import org.apache.hadoop.fs.permission.ChmodParser; |
| import org.apache.hadoop.fs.permission.FsPermission; |
| import org.apache.hadoop.fs.shell.CommandFactory; |
| import org.apache.hadoop.fs.shell.CommandFormat; |
| import org.apache.hadoop.fs.shell.FsCommand; |
| import org.apache.hadoop.fs.shell.PathData; |
| |
| |
| /** |
| * This class is the home for file permissions related commands. |
| * Moved to this separate class since FsShell is getting too large. |
| */ |
| @InterfaceAudience.Private |
| @InterfaceStability.Unstable |
| public class FsShellPermissions extends FsCommand { |
| |
| static Log LOG = FsShell.LOG; |
| |
| /** |
| * Register the permission related commands with the factory |
| * @param factory the command factory |
| */ |
| public static void registerCommands(CommandFactory factory) { |
| factory.addClass(Chmod.class, "-chmod"); |
| factory.addClass(Chown.class, "-chown"); |
| factory.addClass(Chgrp.class, "-chgrp"); |
| } |
| |
| /** |
| * The pattern is almost as flexible as mode allowed by chmod shell command. |
| * The main restriction is that we recognize only rwxXt. To reduce errors we |
| * also enforce octal mode specifications of either 3 digits without a sticky |
| * bit setting or four digits with a sticky bit setting. |
| */ |
| public static class Chmod extends FsShellPermissions { |
| public static final String NAME = "chmod"; |
| public static final String USAGE = "[-R] <MODE[,MODE]... | OCTALMODE> PATH..."; |
| public static final String DESCRIPTION = |
| "Changes permissions of a file.\n" + |
| "\tThis works similar to shell's chmod with a few exceptions.\n\n" + |
| "-R\tmodifies the files recursively. This is the only option\n" + |
| "\tcurrently supported.\n\n" + |
| "MODE\tMode is same as mode used for chmod shell command.\n" + |
| "\tOnly letters recognized are 'rwxXt'. E.g. +t,a+r,g-w,+rwx,o=r\n\n" + |
| "OCTALMODE Mode specifed in 3 or 4 digits. If 4 digits, the first may\n" + |
| "be 1 or 0 to turn the sticky bit on or off, respectively. Unlike " + |
| "shell command, it is not possible to specify only part of the mode\n" + |
| "\tE.g. 754 is same as u=rwx,g=rx,o=r\n\n" + |
| "\tIf none of 'augo' is specified, 'a' is assumed and unlike\n" + |
| "\tshell command, no umask is applied."; |
| |
| protected ChmodParser pp; |
| |
| @Override |
| protected void processOptions(LinkedList<String> args) throws IOException { |
| CommandFormat cf = new CommandFormat(2, Integer.MAX_VALUE, "R", null); |
| cf.parse(args); |
| setRecursive(cf.getOpt("R")); |
| |
| String modeStr = args.removeFirst(); |
| try { |
| pp = new ChmodParser(modeStr); |
| } catch (IllegalArgumentException iea) { |
| // TODO: remove "chmod : " so it's not doubled up in output, but it's |
| // here for backwards compatibility... |
| throw new IllegalArgumentException( |
| "chmod : mode '" + modeStr + "' does not match the expected pattern."); |
| } |
| } |
| |
| @Override |
| protected void processPath(PathData item) throws IOException { |
| short newperms = pp.applyNewPermission(item.stat); |
| if (item.stat.getPermission().toShort() != newperms) { |
| try { |
| item.fs.setPermission(item.path, new FsPermission(newperms)); |
| } catch (IOException e) { |
| LOG.debug("Error changing permissions of " + item, e); |
| throw new IOException( |
| "changing permissions of '" + item + "': " + e.getMessage()); |
| } |
| } |
| } |
| } |
| |
| // used by chown/chgrp |
| static private String allowedChars = "[-_./@a-zA-Z0-9]"; |
| |
| /** |
| * Used to change owner and/or group of files |
| */ |
| public static class Chown extends FsShellPermissions { |
| public static final String NAME = "chown"; |
| public static final String USAGE = "[-R] [OWNER][:[GROUP]] PATH..."; |
| public static final String DESCRIPTION = |
| "Changes owner and group of a file.\n" + |
| "\tThis is similar to shell's chown with a few exceptions.\n\n" + |
| "\t-R\tmodifies the files recursively. This is the only option\n" + |
| "\tcurrently supported.\n\n" + |
| "\tIf only owner or group is specified then only owner or\n" + |
| "\tgroup is modified.\n\n" + |
| "\tThe owner and group names may only cosists of digits, alphabet,\n"+ |
| "\tand any of '-_.@/' i.e. [-_.@/a-zA-Z0-9]. The names are case\n" + |
| "\tsensitive.\n\n" + |
| "\tWARNING: Avoid using '.' to separate user name and group though\n" + |
| "\tLinux allows it. If user names have dots in them and you are\n" + |
| "\tusing local file system, you might see surprising results since\n" + |
| "\tshell command 'chown' is used for local files."; |
| |
| ///allows only "allowedChars" above in names for owner and group |
| static private final Pattern chownPattern = Pattern.compile( |
| "^\\s*(" + allowedChars + "+)?([:](" + allowedChars + "*))?\\s*$"); |
| |
| protected String owner = null; |
| protected String group = null; |
| |
| @Override |
| protected void processOptions(LinkedList<String> args) throws IOException { |
| CommandFormat cf = new CommandFormat(2, Integer.MAX_VALUE, "R"); |
| cf.parse(args); |
| setRecursive(cf.getOpt("R")); |
| parseOwnerGroup(args.removeFirst()); |
| } |
| |
| /** |
| * Parse the first argument into an owner and group |
| * @param ownerStr string describing new ownership |
| */ |
| protected void parseOwnerGroup(String ownerStr) { |
| Matcher matcher = chownPattern.matcher(ownerStr); |
| if (!matcher.matches()) { |
| throw new IllegalArgumentException( |
| "'" + 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 IllegalArgumentException( |
| "'" + ownerStr + "' does not specify owner or group."); |
| } |
| } |
| |
| @Override |
| protected void processPath(PathData item) throws IOException { |
| //Should we do case insensitive match? |
| String newOwner = (owner == null || owner.equals(item.stat.getOwner())) ? |
| null : owner; |
| String newGroup = (group == null || group.equals(item.stat.getGroup())) ? |
| null : group; |
| |
| if (newOwner != null || newGroup != null) { |
| try { |
| item.fs.setOwner(item.path, newOwner, newGroup); |
| } catch (IOException e) { |
| LOG.debug("Error changing ownership of " + item, e); |
| throw new IOException( |
| "changing ownership of '" + item + "': " + e.getMessage()); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Used to change group of files |
| */ |
| public static class Chgrp extends Chown { |
| public static final String NAME = "chgrp"; |
| public static final String USAGE = "[-R] GROUP PATH..."; |
| public static final String DESCRIPTION = |
| "This is equivalent to -chown ... :GROUP ..."; |
| |
| static private final Pattern chgrpPattern = |
| Pattern.compile("^\\s*(" + allowedChars + "+)\\s*$"); |
| |
| @Override |
| protected void parseOwnerGroup(String groupStr) { |
| Matcher matcher = chgrpPattern.matcher(groupStr); |
| if (!matcher.matches()) { |
| throw new IllegalArgumentException( |
| "'" + groupStr + "' does not match expected pattern for group"); |
| } |
| owner = null; |
| group = matcher.group(1); |
| } |
| } |
| } |