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 */ 018package org.apache.hadoop.fs; 019 020import java.io.IOException; 021import java.util.LinkedList; 022import java.util.regex.Matcher; 023import java.util.regex.Pattern; 024 025import org.apache.commons.logging.Log; 026import org.apache.hadoop.classification.InterfaceAudience; 027import org.apache.hadoop.classification.InterfaceStability; 028import org.apache.hadoop.fs.permission.ChmodParser; 029import org.apache.hadoop.fs.permission.FsPermission; 030import org.apache.hadoop.fs.shell.CommandFactory; 031import org.apache.hadoop.fs.shell.CommandFormat; 032import org.apache.hadoop.fs.shell.FsCommand; 033import org.apache.hadoop.fs.shell.PathData; 034 035 036/** 037 * This class is the home for file permissions related commands. 038 * Moved to this separate class since FsShell is getting too large. 039 */ 040@InterfaceAudience.Private 041@InterfaceStability.Unstable 042public class FsShellPermissions extends FsCommand { 043 044 static Log LOG = FsShell.LOG; 045 046 /** 047 * Register the permission related commands with the factory 048 * @param factory the command factory 049 */ 050 public static void registerCommands(CommandFactory factory) { 051 factory.addClass(Chmod.class, "-chmod"); 052 factory.addClass(Chown.class, "-chown"); 053 factory.addClass(Chgrp.class, "-chgrp"); 054 } 055 056 /** 057 * The pattern is almost as flexible as mode allowed by chmod shell command. 058 * The main restriction is that we recognize only rwxXt. To reduce errors we 059 * also enforce octal mode specifications of either 3 digits without a sticky 060 * bit setting or four digits with a sticky bit setting. 061 */ 062 public static class Chmod extends FsShellPermissions { 063 public static final String NAME = "chmod"; 064 public static final String USAGE = "[-R] <MODE[,MODE]... | OCTALMODE> PATH..."; 065 public static final String DESCRIPTION = 066 "Changes permissions of a file.\n" + 067 "\tThis works similar to shell's chmod with a few exceptions.\n\n" + 068 "-R\tmodifies the files recursively. This is the only option\n" + 069 "\tcurrently supported.\n\n" + 070 "MODE\tMode is same as mode used for chmod shell command.\n" + 071 "\tOnly letters recognized are 'rwxXt'. E.g. +t,a+r,g-w,+rwx,o=r\n\n" + 072 "OCTALMODE Mode specifed in 3 or 4 digits. If 4 digits, the first may\n" + 073 "be 1 or 0 to turn the sticky bit on or off, respectively. Unlike " + 074 "shell command, it is not possible to specify only part of the mode\n" + 075 "\tE.g. 754 is same as u=rwx,g=rx,o=r\n\n" + 076 "\tIf none of 'augo' is specified, 'a' is assumed and unlike\n" + 077 "\tshell command, no umask is applied."; 078 079 protected ChmodParser pp; 080 081 @Override 082 protected void processOptions(LinkedList<String> args) throws IOException { 083 CommandFormat cf = new CommandFormat(2, Integer.MAX_VALUE, "R", null); 084 cf.parse(args); 085 setRecursive(cf.getOpt("R")); 086 087 String modeStr = args.removeFirst(); 088 try { 089 pp = new ChmodParser(modeStr); 090 } catch (IllegalArgumentException iea) { 091 // TODO: remove "chmod : " so it's not doubled up in output, but it's 092 // here for backwards compatibility... 093 throw new IllegalArgumentException( 094 "chmod : mode '" + modeStr + "' does not match the expected pattern."); 095 } 096 } 097 098 @Override 099 protected void processPath(PathData item) throws IOException { 100 short newperms = pp.applyNewPermission(item.stat); 101 if (item.stat.getPermission().toShort() != newperms) { 102 try { 103 item.fs.setPermission(item.path, new FsPermission(newperms)); 104 } catch (IOException e) { 105 LOG.debug("Error changing permissions of " + item, e); 106 throw new IOException( 107 "changing permissions of '" + item + "': " + e.getMessage()); 108 } 109 } 110 } 111 } 112 113 // used by chown/chgrp 114 static private String allowedChars = "[-_./@a-zA-Z0-9]"; 115 116 /** 117 * Used to change owner and/or group of files 118 */ 119 public static class Chown extends FsShellPermissions { 120 public static final String NAME = "chown"; 121 public static final String USAGE = "[-R] [OWNER][:[GROUP]] PATH..."; 122 public static final String DESCRIPTION = 123 "Changes owner and group of a file.\n" + 124 "\tThis is similar to shell's chown with a few exceptions.\n\n" + 125 "\t-R\tmodifies the files recursively. This is the only option\n" + 126 "\tcurrently supported.\n\n" + 127 "\tIf only owner or group is specified then only owner or\n" + 128 "\tgroup is modified.\n\n" + 129 "\tThe owner and group names may only cosists of digits, alphabet,\n"+ 130 "\tand any of '-_.@/' i.e. [-_.@/a-zA-Z0-9]. The names are case\n" + 131 "\tsensitive.\n\n" + 132 "\tWARNING: Avoid using '.' to separate user name and group though\n" + 133 "\tLinux allows it. If user names have dots in them and you are\n" + 134 "\tusing local file system, you might see surprising results since\n" + 135 "\tshell command 'chown' is used for local files."; 136 137 ///allows only "allowedChars" above in names for owner and group 138 static private final Pattern chownPattern = Pattern.compile( 139 "^\\s*(" + allowedChars + "+)?([:](" + allowedChars + "*))?\\s*$"); 140 141 protected String owner = null; 142 protected String group = null; 143 144 @Override 145 protected void processOptions(LinkedList<String> args) throws IOException { 146 CommandFormat cf = new CommandFormat(2, Integer.MAX_VALUE, "R"); 147 cf.parse(args); 148 setRecursive(cf.getOpt("R")); 149 parseOwnerGroup(args.removeFirst()); 150 } 151 152 /** 153 * Parse the first argument into an owner and group 154 * @param ownerStr string describing new ownership 155 */ 156 protected void parseOwnerGroup(String ownerStr) { 157 Matcher matcher = chownPattern.matcher(ownerStr); 158 if (!matcher.matches()) { 159 throw new IllegalArgumentException( 160 "'" + ownerStr + "' does not match expected pattern for [owner][:group]."); 161 } 162 owner = matcher.group(1); 163 group = matcher.group(3); 164 if (group != null && group.length() == 0) { 165 group = null; 166 } 167 if (owner == null && group == null) { 168 throw new IllegalArgumentException( 169 "'" + ownerStr + "' does not specify owner or group."); 170 } 171 } 172 173 @Override 174 protected void processPath(PathData item) throws IOException { 175 //Should we do case insensitive match? 176 String newOwner = (owner == null || owner.equals(item.stat.getOwner())) ? 177 null : owner; 178 String newGroup = (group == null || group.equals(item.stat.getGroup())) ? 179 null : group; 180 181 if (newOwner != null || newGroup != null) { 182 try { 183 item.fs.setOwner(item.path, newOwner, newGroup); 184 } catch (IOException e) { 185 LOG.debug("Error changing ownership of " + item, e); 186 throw new IOException( 187 "changing ownership of '" + item + "': " + e.getMessage()); 188 } 189 } 190 } 191 } 192 193 /** 194 * Used to change group of files 195 */ 196 public static class Chgrp extends Chown { 197 public static final String NAME = "chgrp"; 198 public static final String USAGE = "[-R] GROUP PATH..."; 199 public static final String DESCRIPTION = 200 "This is equivalent to -chown ... :GROUP ..."; 201 202 static private final Pattern chgrpPattern = 203 Pattern.compile("^\\s*(" + allowedChars + "+)\\s*$"); 204 205 @Override 206 protected void parseOwnerGroup(String groupStr) { 207 Matcher matcher = chgrpPattern.matcher(groupStr); 208 if (!matcher.matches()) { 209 throw new IllegalArgumentException( 210 "'" + groupStr + "' does not match expected pattern for group"); 211 } 212 owner = null; 213 group = matcher.group(1); 214 } 215 } 216}