/**
* 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.shell;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import com.google.common.collect.Lists;
import org.apache.hadoop.HadoopIllegalArgumentException;
import org.apache.hadoop.classification.InterfaceAudience;
import org.apache.hadoop.classification.InterfaceStability;
import org.apache.hadoop.fs.permission.AclEntry;
import org.apache.hadoop.fs.permission.AclEntryScope;
import org.apache.hadoop.fs.permission.AclEntryType;
import org.apache.hadoop.fs.permission.AclStatus;
import org.apache.hadoop.fs.permission.FsAction;
import org.apache.hadoop.fs.permission.FsPermission;
/**
* Acl related operations
*/
@InterfaceAudience.Private
@InterfaceStability.Evolving
class AclCommands extends FsCommand {
private static String GET_FACL = "getfacl";
private static String SET_FACL = "setfacl";
public static void registerCommands(CommandFactory factory) {
factory.addClass(GetfaclCommand.class, "-" + GET_FACL);
factory.addClass(SetfaclCommand.class, "-" + SET_FACL);
}
/**
* Implementing the '-getfacl' command for the the FsShell.
*/
public static class GetfaclCommand extends FsCommand {
public static String NAME = GET_FACL;
public static String USAGE = "[-R] <path>";
public static String DESCRIPTION = "Displays the Access Control Lists"
+ " (ACLs) of files and directories. If a directory has a default ACL,"
+ " then getfacl also displays the default ACL.\n"
+ "-R: List the ACLs of all files and directories recursively.\n"
+ "<path>: File or directory to list.\n";
@Override
protected void processOptions(LinkedList<String> args) throws IOException {
CommandFormat cf = new CommandFormat(0, Integer.MAX_VALUE, "R");
cf.parse(args);
setRecursive(cf.getOpt("R"));
if (args.isEmpty()) {
throw new HadoopIllegalArgumentException("<path> is missing");
}
if (args.size() > 1) {
throw new HadoopIllegalArgumentException("Too many arguments");
}
}
@Override
protected void processPath(PathData item) throws IOException {
AclStatus aclStatus = item.fs.getAclStatus(item.path);
out.println("# file: " + item);
out.println("# owner: " + aclStatus.getOwner());
out.println("# group: " + aclStatus.getGroup());
List<AclEntry> entries = aclStatus.getEntries();
if (aclStatus.isStickyBit()) {
String stickyFlag = "T";
for (AclEntry aclEntry : entries) {
if (aclEntry.getType() == AclEntryType.OTHER
&& aclEntry.getScope() == AclEntryScope.ACCESS
&& aclEntry.getPermission().implies(FsAction.EXECUTE)) {
stickyFlag = "t";
break;
}
}
out.println("# flags: --" + stickyFlag);
}
FsPermission perm = item.stat.getPermission();
if (entries.isEmpty()) {
printMinimalAcl(perm);
} else {
printExtendedAcl(perm, entries);
}
out.println();
}
/**
* Prints an extended ACL, including all extended ACL entries and also the
* base entries implied by the permission bits.
*
* @param perm FsPermission of file
* @param entries List<AclEntry> containing ACL entries of file
*/
private void printExtendedAcl(FsPermission perm, List<AclEntry> entries) {
// Print owner entry implied by owner permission bits.
out.println(new AclEntry.Builder()
.setScope(AclEntryScope.ACCESS)
.setType(AclEntryType.USER)
.setPermission(perm.getUserAction())
.build());
// Print all extended access ACL entries.
boolean hasAccessAcl = false;
Iterator<AclEntry> entryIter = entries.iterator();
AclEntry curEntry = null;
while (entryIter.hasNext()) {
curEntry = entryIter.next();
if (curEntry.getScope() == AclEntryScope.DEFAULT) {
break;
}
hasAccessAcl = true;
printExtendedAclEntry(curEntry, perm.getGroupAction());
}
// Print mask entry implied by group permission bits, or print group entry
// if there is no access ACL (only default ACL).
out.println(new AclEntry.Builder()
.setScope(AclEntryScope.ACCESS)
.setType(hasAccessAcl ? AclEntryType.MASK : AclEntryType.GROUP)
.setPermission(perm.getGroupAction())
.build());
// Print other entry implied by other bits.
out.println(new AclEntry.Builder()
.setScope(AclEntryScope.ACCESS)
.setType(AclEntryType.OTHER)
.setPermission(perm.getOtherAction())
.build());
// Print default ACL entries.
if (curEntry != null && curEntry.getScope() == AclEntryScope.DEFAULT) {
out.println(curEntry);
// ACL sort order guarantees default mask is the second-to-last entry.
FsAction maskPerm = entries.get(entries.size() - 2).getPermission();
while (entryIter.hasNext()) {
printExtendedAclEntry(entryIter.next(), maskPerm);
}
}
}
/**
* Prints a single extended ACL entry. If the mask restricts the
* permissions of the entry, then also prints the restricted version as the
* effective permissions. The mask applies to all named entries and also
* the unnamed group entry.
*
* @param entry AclEntry extended ACL entry to print
* @param maskPerm FsAction permissions in the ACL's mask entry
*/
private void printExtendedAclEntry(AclEntry entry, FsAction maskPerm) {
if (entry.getName() != null || entry.getType() == AclEntryType.GROUP) {
FsAction entryPerm = entry.getPermission();
FsAction effectivePerm = entryPerm.and(maskPerm);
if (entryPerm != effectivePerm) {
out.println(String.format("%s\t#effective:%s", entry,
effectivePerm.SYMBOL));
} else {
out.println(entry);
}
} else {
out.println(entry);
}
}
/**
* Prints a minimal ACL, consisting of exactly 3 ACL entries implied by the
* permission bits.
*
* @param perm FsPermission of file
*/
private void printMinimalAcl(FsPermission perm) {
out.println(new AclEntry.Builder()
.setScope(AclEntryScope.ACCESS)
.setType(AclEntryType.USER)
.setPermission(perm.getUserAction())
.build());
out.println(new AclEntry.Builder()
.setScope(AclEntryScope.ACCESS)
.setType(AclEntryType.GROUP)
.setPermission(perm.getGroupAction())
.build());
out.println(new AclEntry.Builder()
.setScope(AclEntryScope.ACCESS)
.setType(AclEntryType.OTHER)
.setPermission(perm.getOtherAction())
.build());
}
}
/**
* Implementing the '-setfacl' command for the the FsShell.
*/
public static class SetfaclCommand extends FsCommand {
public static String NAME = SET_FACL;
public static String USAGE = "[-R] [{-b|-k} {-m|-x <acl_spec>} <path>]"
+ "|[--set <acl_spec> <path>]";
public static String DESCRIPTION = "Sets Access Control Lists (ACLs)"
+ " of files and directories.\n"
+ "Options:\n"
+ "-b :Remove all but the base ACL entries. The entries for user,"
+ " group and others are retained for compatibility with permission "
+ "bits.\n"
+ "-k :Remove the default ACL.\n"
+ "-R :Apply operations to all files and directories recursively.\n"
+ "-m :Modify ACL. New entries are added to the ACL, and existing"
+ " entries are retained.\n"
+ "-x :Remove specified ACL entries. Other ACL entries are retained.\n"
+ "--set :Fully replace the ACL, discarding all existing entries."
+ " The <acl_spec> must include entries for user, group, and others"
+ " for compatibility with permission bits.\n"
+ "<acl_spec>: Comma separated list of ACL entries.\n"
+ "<path>: File or directory to modify.\n";
CommandFormat cf = new CommandFormat(0, Integer.MAX_VALUE, "b", "k", "R",
"m", "x", "-set");
List<AclEntry> aclEntries = null;
List<AclEntry> accessAclEntries = null;
@Override
protected void processOptions(LinkedList<String> args) throws IOException {
cf.parse(args);
setRecursive(cf.getOpt("R"));
// Mix of remove and modify acl flags are not allowed
boolean bothRemoveOptions = cf.getOpt("b") && cf.getOpt("k");
boolean bothModifyOptions = cf.getOpt("m") && cf.getOpt("x");
boolean oneRemoveOption = cf.getOpt("b") || cf.getOpt("k");
boolean oneModifyOption = cf.getOpt("m") || cf.getOpt("x");
boolean setOption = cf.getOpt("-set");
if ((bothRemoveOptions || bothModifyOptions)
|| (oneRemoveOption && oneModifyOption)
|| (setOption && (oneRemoveOption || oneModifyOption))) {
throw new HadoopIllegalArgumentException(
"Specified flags contains both remove and modify flags");
}
// Only -m, -x and --set expects <acl_spec>
if (oneModifyOption || setOption) {
if (args.size() < 2) {
throw new HadoopIllegalArgumentException("<acl_spec> is missing");
}
aclEntries = AclEntry.parseAclSpec(args.removeFirst(), !cf.getOpt("x"));
}
if (args.isEmpty()) {
throw new HadoopIllegalArgumentException("<path> is missing");
}
if (args.size() > 1) {
throw new HadoopIllegalArgumentException("Too many arguments");
}
// In recursive mode, save a separate list of just the access ACL entries.
// Only directories may have a default ACL. When a recursive operation
// encounters a file under the specified path, it must pass only the
// access ACL entries.
if (isRecursive() && (oneModifyOption || setOption)) {
accessAclEntries = Lists.newArrayList();
for (AclEntry entry: aclEntries) {
if (entry.getScope() == AclEntryScope.ACCESS) {
accessAclEntries.add(entry);
}
}
}
}
@Override
protected void processPath(PathData item) throws IOException {
if (cf.getOpt("b")) {
item.fs.removeAcl(item.path);
} else if (cf.getOpt("k")) {
item.fs.removeDefaultAcl(item.path);
} else if (cf.getOpt("m")) {
List<AclEntry> entries = getAclEntries(item);
if (!entries.isEmpty()) {
item.fs.modifyAclEntries(item.path, entries);
}
} else if (cf.getOpt("x")) {
List<AclEntry> entries = getAclEntries(item);
if (!entries.isEmpty()) {
item.fs.removeAclEntries(item.path, entries);
}
} else if (cf.getOpt("-set")) {
List<AclEntry> entries = getAclEntries(item);
if (!entries.isEmpty()) {
item.fs.setAcl(item.path, entries);
}
}
}
/**
* Returns the ACL entries to use in the API call for the given path. For a
* recursive operation, returns all specified ACL entries if the item is a
* directory or just the access ACL entries if the item is a file. For a
* non-recursive operation, returns all specified ACL entries.
*
* @param item PathData path to check
* @return List<AclEntry> ACL entries to use in the API call
*/
private List<AclEntry> getAclEntries(PathData item) {
if (isRecursive()) {
return item.stat.isDirectory() ? aclEntries : accessAclEntries;
} else {
return aclEntries;
}
}
}
}