| /** |
| * 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.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; |
| import org.apache.hadoop.util.Shell; |
| import org.slf4j.Logger; |
| |
| /** |
| * 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 final Logger 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. " + |
| "This works similar to the shell's chmod command with a few exceptions.\n" + |
| "-R: modifies the files recursively. This is the only option" + |
| " currently supported.\n" + |
| "<MODE>: Mode is the same as mode used for the shell's command. " + |
| "The only letters recognized are 'rwxXt', e.g. +t,a+r,g-w,+rwx,o=r.\n" + |
| "<OCTALMODE>: Mode specifed in 3 or 4 digits. If 4 digits, the first " + |
| "may be 1 or 0 to turn the sticky bit on or off, respectively. Unlike " + |
| "the shell command, it is not possible to specify only part of the " + |
| "mode, e.g. 754 is same as u=rwx,g=rx,o=r.\n\n" + |
| "If none of 'augo' is specified, 'a' is assumed and unlike the " + |
| "shell 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 = Shell.WINDOWS ? "[-_./@a-zA-Z0-9 ]" : |
| "[-_./@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. " + |
| "This is similar to the shell's chown command with a few exceptions.\n" + |
| "-R: modifies the files recursively. This is the only option " + |
| "currently supported.\n\n" + |
| "If only the owner or group is specified, then only the owner or " + |
| "group is modified. " + |
| "The owner and group names may only consist of digits, alphabet, "+ |
| "and any of " + allowedChars + ". The names are case sensitive.\n\n" + |
| "WARNING: Avoid using '.' to separate user name and group though " + |
| "Linux allows it. If user names have dots in them and you are " + |
| "using local file system, you might see surprising results since " + |
| "the shell 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); |
| } |
| } |
| } |