diff --git a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/AclCommands.java b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/AclCommands.java index 5fad5f0ad39..cb83027f517 100644 --- a/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/AclCommands.java +++ b/hadoop-common-project/hadoop-common/src/main/java/org/apache/hadoop/fs/shell/AclCommands.java @@ -20,6 +20,7 @@ package org.apache.hadoop.fs.shell; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; +import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -32,6 +33,7 @@ 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; import org.apache.hadoop.util.StringUtils; /** @@ -92,10 +94,87 @@ class AclCommands extends FsCommand { } out.println("# flags: --" + stickyFlag); } - for (AclEntry entry : entries) { - out.println(entry); + + FsPermission perm = item.stat.getPermission(); + if (perm.getAclBit()) { + printExtendedAcl(perm, entries); + } else { + printMinimalAcl(perm); } } + + /** + * 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 containing ACL entries of file + */ + private void printExtendedAcl(FsPermission perm, List 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. + Iterator entryIter = entries.iterator(); + AclEntry curEntry = null; + while (entryIter.hasNext()) { + curEntry = entryIter.next(); + if (curEntry.getScope() == AclEntryScope.DEFAULT) { + break; + } + out.println(curEntry); + } + + // Print mask entry implied by group permission bits. + out.println(new AclEntry.Builder() + .setScope(AclEntryScope.ACCESS) + .setType(AclEntryType.MASK) + .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); + } + while (entryIter.hasNext()) { + out.println(entryIter.next()); + } + } + + /** + * 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()); + } } /** diff --git a/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt b/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt index ae68ba90cae..156d90f4032 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt +++ b/hadoop-hdfs-project/hadoop-hdfs/CHANGES-HDFS-4685.txt @@ -30,6 +30,9 @@ HDFS-4685 (Unreleased) HADOOP-10220. Add ACL indicator bit to FsPermission. (cnauroth) + HDFS-5758. NameNode: complete implementation of inode modifications for + ACLs. (Chris Nauroth via wheat9) + OPTIMIZATIONS BUG FIXES diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSClient.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSClient.java index 726a097ec95..478b7503db9 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSClient.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/DFSClient.java @@ -111,6 +111,7 @@ import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hdfs.client.ClientMmapManager; import org.apache.hadoop.hdfs.client.HdfsDataInputStream; import org.apache.hadoop.hdfs.client.HdfsDataOutputStream; +import org.apache.hadoop.hdfs.protocol.AclException; import org.apache.hadoop.hdfs.protocol.CacheDirectiveEntry; import org.apache.hadoop.hdfs.protocol.CacheDirectiveInfo; import org.apache.hadoop.hdfs.protocol.CacheDirectiveIterator; @@ -2648,10 +2649,11 @@ public class DFSClient implements java.io.Closeable { namenode.modifyAclEntries(src, aclSpec); } catch(RemoteException re) { throw re.unwrapRemoteException(AccessControlException.class, + AclException.class, FileNotFoundException.class, SafeModeException.class, - UnresolvedPathException.class, - SnapshotAccessControlException.class); + SnapshotAccessControlException.class, + UnresolvedPathException.class); } } @@ -2662,10 +2664,11 @@ public class DFSClient implements java.io.Closeable { namenode.removeAclEntries(src, aclSpec); } catch(RemoteException re) { throw re.unwrapRemoteException(AccessControlException.class, + AclException.class, FileNotFoundException.class, SafeModeException.class, - UnresolvedPathException.class, - SnapshotAccessControlException.class); + SnapshotAccessControlException.class, + UnresolvedPathException.class); } } @@ -2675,10 +2678,11 @@ public class DFSClient implements java.io.Closeable { namenode.removeDefaultAcl(src); } catch(RemoteException re) { throw re.unwrapRemoteException(AccessControlException.class, + AclException.class, FileNotFoundException.class, SafeModeException.class, - UnresolvedPathException.class, - SnapshotAccessControlException.class); + SnapshotAccessControlException.class, + UnresolvedPathException.class); } } @@ -2688,10 +2692,11 @@ public class DFSClient implements java.io.Closeable { namenode.removeAcl(src); } catch(RemoteException re) { throw re.unwrapRemoteException(AccessControlException.class, + AclException.class, FileNotFoundException.class, SafeModeException.class, - UnresolvedPathException.class, - SnapshotAccessControlException.class); + SnapshotAccessControlException.class, + UnresolvedPathException.class); } } @@ -2701,10 +2706,11 @@ public class DFSClient implements java.io.Closeable { namenode.setAcl(src, aclSpec); } catch(RemoteException re) { throw re.unwrapRemoteException(AccessControlException.class, + AclException.class, FileNotFoundException.class, SafeModeException.class, - UnresolvedPathException.class, - SnapshotAccessControlException.class); + SnapshotAccessControlException.class, + UnresolvedPathException.class); } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/AclStorage.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/AclStorage.java new file mode 100644 index 00000000000..fa53f55275b --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/AclStorage.java @@ -0,0 +1,291 @@ +/** + * 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.hdfs.server.namenode; + +import java.util.Collections; +import java.util.List; + +import com.google.common.collect.Lists; + +import org.apache.hadoop.classification.InterfaceAudience; +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.FsPermission; +import org.apache.hadoop.hdfs.protocol.AclException; +import org.apache.hadoop.hdfs.protocol.QuotaExceededException; + +/** + * AclStorage contains utility methods that define how ACL data is stored in the + * namespace. + * + * If an inode has an ACL, then the ACL bit is set in the inode's + * {@link FsPermission} and the inode also contains an {@link AclFeature}. For + * the access ACL, the owner and other entries are identical to the owner and + * other bits stored in FsPermission, so we reuse those. The access mask entry + * is stored into the group permission bits of FsPermission. This is consistent + * with other file systems' implementations of ACLs and eliminates the need for + * special handling in various parts of the codebase. For example, if a user + * calls chmod to change group permission bits on a file with an ACL, then the + * expected behavior is to change the ACL's mask entry. By saving the mask entry + * into the group permission bits, chmod continues to work correctly without + * special handling. All remaining access entries (named users and named groups) + * are stored as explicit {@link AclEntry} instances in a list inside the + * AclFeature. Additionally, all default entries are stored in the AclFeature. + * + * The methods in this class encapsulate these rules for reading or writing the + * ACL entries to the appropriate location. + * + * The methods in this class assume that input ACL entry lists have already been + * validated and sorted according to the rules enforced by + * {@link AclTransformation}. + */ +@InterfaceAudience.Private +final class AclStorage { + + /** + * Reads the existing extended ACL entries of an inode. This method returns + * only the extended ACL entries stored in the AclFeature. If the inode does + * not have an ACL, then this method returns an empty list. + * + * @param inode INodeWithAdditionalFields to read + * @param snapshotId int latest snapshot ID of inode + * @return List containing extended inode ACL entries + */ + public static List readINodeAcl(INodeWithAdditionalFields inode, + int snapshotId) { + FsPermission perm = inode.getPermissionStatus(snapshotId).getPermission(); + if (perm.getAclBit()) { + return inode.getAclFeature().getEntries(); + } else { + return Collections.emptyList(); + } + } + + /** + * Reads the existing ACL of an inode. This method always returns the full + * logical ACL of the inode after reading relevant data from the inode's + * {@link FsPermission} and {@link AclFeature}. Note that every inode + * logically has an ACL, even if no ACL has been set explicitly. If the inode + * does not have an extended ACL, then the result is a minimal ACL consising of + * exactly 3 entries that correspond to the owner, group and other permissions. + * + * @param inode INodeWithAdditionalFields to read + * @param snapshotId int latest snapshot ID of inode + * @return List containing all logical inode ACL entries + */ + public static List readINodeLogicalAcl( + INodeWithAdditionalFields inode, int snapshotId) { + final List existingAcl; + FsPermission perm = inode.getPermissionStatus(snapshotId).getPermission(); + if (perm.getAclBit()) { + // Split ACL entries stored in the feature into access vs. default. + List featureEntries = inode.getAclFeature().getEntries(); + ScopedAclEntries scoped = new ScopedAclEntries(featureEntries); + List accessEntries = scoped.getAccessEntries(); + List defaultEntries = scoped.getDefaultEntries(); + + // Pre-allocate list size for the explicit entries stored in the feature + // plus the 3 implicit entries (owner, group and other) from the permission + // bits. + existingAcl = Lists.newArrayListWithCapacity(featureEntries.size() + 3); + + if (!accessEntries.isEmpty()) { + // Add owner entry implied from user permission bits. + existingAcl.add(new AclEntry.Builder() + .setScope(AclEntryScope.ACCESS) + .setType(AclEntryType.USER) + .setPermission(perm.getUserAction()) + .build()); + + // Next add all named user and group entries taken from the feature. + existingAcl.addAll(accessEntries); + + // Add mask entry implied from group permission bits. + existingAcl.add(new AclEntry.Builder() + .setScope(AclEntryScope.ACCESS) + .setType(AclEntryType.MASK) + .setPermission(perm.getGroupAction()) + .build()); + + // Add other entry implied from other permission bits. + existingAcl.add(new AclEntry.Builder() + .setScope(AclEntryScope.ACCESS) + .setType(AclEntryType.OTHER) + .setPermission(perm.getOtherAction()) + .build()); + } else { + // It's possible that there is a default ACL but no access ACL. In this + // case, add the minimal access ACL implied by the permission bits. + existingAcl.addAll(getMinimalAcl(perm)); + } + + // Add all default entries after the access entries. + existingAcl.addAll(defaultEntries); + } else { + // If the inode doesn't have an extended ACL, then return a minimal ACL. + existingAcl = getMinimalAcl(perm); + } + + // The above adds entries in the correct order, so no need to sort here. + return existingAcl; + } + + /** + * Completely removes the ACL from an inode. + * + * @param inode INodeWithAdditionalFields to update + * @param snapshotId int latest snapshot ID of inode + * @throws QuotaExceededException if quota limit is exceeded + */ + public static void removeINodeAcl(INodeWithAdditionalFields inode, + int snapshotId) throws QuotaExceededException { + FsPermission perm = inode.getPermissionStatus(snapshotId).getPermission(); + if (perm.getAclBit()) { + // Restore group permissions from the feature's entry to permission bits, + // overwriting the mask, which is not part of a minimal ACL. + List featureEntries = inode.getAclFeature().getEntries(); + AclEntry groupEntryKey = new AclEntry.Builder() + .setScope(AclEntryScope.ACCESS) + .setType(AclEntryType.GROUP) + .build(); + int groupEntryIndex = Collections.binarySearch(featureEntries, + groupEntryKey, AclTransformation.ACL_ENTRY_COMPARATOR); + assert groupEntryIndex >= 0; + + // Remove the feature and turn off the ACL bit. + inode.removeAclFeature(); + FsPermission newPerm = new FsPermission(perm.getUserAction(), + featureEntries.get(groupEntryIndex).getPermission(), + perm.getOtherAction(), + perm.getStickyBit(), false); + inode.setPermission(newPerm, snapshotId); + } + } + + /** + * Updates an inode with a new ACL. This method takes a full logical ACL and + * stores the entries to the inode's {@link FsPermission} and + * {@link AclFeature}. + * + * @param inode INodeWithAdditionalFields to update + * @param newAcl List containing new ACL entries + * @param snapshotId int latest snapshot ID of inode + * @throws AclException if the ACL is invalid for the given inode + * @throws QuotaExceededException if quota limit is exceeded + */ + public static void updateINodeAcl(INodeWithAdditionalFields inode, + List newAcl, int snapshotId) throws AclException, + QuotaExceededException { + assert newAcl.size() >= 3; + FsPermission perm = inode.getPermissionStatus(snapshotId).getPermission(); + final FsPermission newPerm; + if (newAcl.size() > 3) { + // This is an extended ACL. Split entries into access vs. default. + ScopedAclEntries scoped = new ScopedAclEntries(newAcl); + List accessEntries = scoped.getAccessEntries(); + List defaultEntries = scoped.getDefaultEntries(); + + // Only directories may have a default ACL. + if (!defaultEntries.isEmpty() && !inode.isDirectory()) { + throw new AclException( + "Invalid ACL: only directories may have a default ACL."); + } + + // Pre-allocate list size for the explicit entries stored in the feature, + // which is all entries minus the 3 entries implicitly stored in the + // permission bits. + List featureEntries = Lists.newArrayListWithCapacity( + (accessEntries.size() - 3) + defaultEntries.size()); + + // Calculate new permission bits. For a correctly sorted ACL, the first + // entry is the owner and the last 2 entries are the mask and other entries + // respectively. Also preserve sticky bit and toggle ACL bit on. + newPerm = new FsPermission(accessEntries.get(0).getPermission(), + accessEntries.get(accessEntries.size() - 2).getPermission(), + accessEntries.get(accessEntries.size() - 1).getPermission(), + perm.getStickyBit(), true); + + // For the access ACL, the feature only needs to hold the named user and + // group entries. For a correctly sorted ACL, these will be in a + // predictable range. + if (accessEntries.size() > 3) { + featureEntries.addAll( + accessEntries.subList(1, accessEntries.size() - 2)); + } + + // Add all default entries to the feature. + featureEntries.addAll(defaultEntries); + + // Attach entries to the feature, creating a new feature if needed. + AclFeature aclFeature = inode.getAclFeature(); + if (aclFeature == null) { + aclFeature = new AclFeature(); + inode.addAclFeature(aclFeature); + } + aclFeature.setEntries(featureEntries); + } else { + // This is a minimal ACL. Remove the ACL feature if it previously had one. + if (perm.getAclBit()) { + inode.removeAclFeature(); + } + + // Calculate new permission bits. For a correctly sorted ACL, the owner, + // group and other permissions are in order. Also preserve sticky bit and + // toggle ACL bit off. + newPerm = new FsPermission(newAcl.get(0).getPermission(), + newAcl.get(1).getPermission(), + newAcl.get(2).getPermission(), + perm.getStickyBit(), false); + } + + inode.setPermission(newPerm, snapshotId); + } + + /** + * There is no reason to instantiate this class. + */ + private AclStorage() { + } + + /** + * Translates the given permission bits to the equivalent minimal ACL. + * + * @param perm FsPermission to translate + * @return List containing exactly 3 entries representing the owner, + * group and other permissions + */ + private static List getMinimalAcl(FsPermission perm) { + return Lists.newArrayList( + new AclEntry.Builder() + .setScope(AclEntryScope.ACCESS) + .setType(AclEntryType.USER) + .setPermission(perm.getUserAction()) + .build(), + new AclEntry.Builder() + .setScope(AclEntryScope.ACCESS) + .setType(AclEntryType.GROUP) + .setPermission(perm.getGroupAction()) + .build(), + new AclEntry.Builder() + .setScope(AclEntryScope.ACCESS) + .setType(AclEntryType.OTHER) + .setPermission(perm.getOtherAction()) + .build()); + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/AclTransformation.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/AclTransformation.java index 2f52268ecf1..44a2f3dd118 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/AclTransformation.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/AclTransformation.java @@ -62,7 +62,6 @@ import org.apache.hadoop.hdfs.protocol.AclException; @InterfaceAudience.Private final class AclTransformation { private static final int MAX_ENTRIES = 32; - private static final int PIVOT_NOT_FOUND = -1; /** * Filters (discards) any existing ACL entries that have the same scope, type @@ -246,7 +245,7 @@ final class AclTransformation { * -other entry * All access ACL entries sort ahead of all default ACL entries. */ - private static final Comparator ACL_ENTRY_COMPARATOR = + static final Comparator ACL_ENTRY_COMPARATOR = new Comparator() { @Override public int compare(AclEntry entry1, AclEntry entry2) { @@ -294,20 +293,20 @@ final class AclTransformation { } // Search for the required base access entries. If there is a default ACL, // then do the same check on the default entries. - int pivot = calculatePivotOnDefaultEntries(aclBuilder); + ScopedAclEntries scopedEntries = new ScopedAclEntries(aclBuilder); for (AclEntryType type: EnumSet.of(USER, GROUP, OTHER)) { AclEntry accessEntryKey = new AclEntry.Builder().setScope(ACCESS) .setType(type).build(); - if (Collections.binarySearch(aclBuilder, accessEntryKey, - ACL_ENTRY_COMPARATOR) < 0) { + if (Collections.binarySearch(scopedEntries.getAccessEntries(), + accessEntryKey, ACL_ENTRY_COMPARATOR) < 0) { throw new AclException( "Invalid ACL: the user, group and other entries are required."); } - if (pivot != PIVOT_NOT_FOUND) { + if (!scopedEntries.getDefaultEntries().isEmpty()) { AclEntry defaultEntryKey = new AclEntry.Builder().setScope(DEFAULT) .setType(type).build(); - if (Collections.binarySearch(aclBuilder, defaultEntryKey, - ACL_ENTRY_COMPARATOR) < 0) { + if (Collections.binarySearch(scopedEntries.getDefaultEntries(), + defaultEntryKey, ACL_ENTRY_COMPARATOR) < 0) { throw new AclException( "Invalid default ACL: the user, group and other entries are required."); } @@ -364,12 +363,17 @@ final class AclTransformation { for (AclEntryScope scope: scopeFound) { if (!providedMask.containsKey(scope) && maskNeeded.contains(scope) && maskDirty.contains(scope)) { + // Caller explicitly removed mask entry, but it's required. throw new AclException( "Invalid ACL: mask is required, but it was deleted."); } else if (providedMask.containsKey(scope) && (!scopeDirty.contains(scope) || maskDirty.contains(scope))) { + // Caller explicitly provided new mask, or we are preserving the existing + // mask in an unchanged scope. aclBuilder.add(providedMask.get(scope)); - } else if (maskNeeded.contains(scope)) { + } else if (maskNeeded.contains(scope) || providedMask.containsKey(scope)) { + // Otherwise, if there are maskable entries present, or the ACL + // previously had a mask, then recalculate a mask automatically. aclBuilder.add(new AclEntry.Builder() .setScope(scope) .setType(MASK) @@ -379,23 +383,6 @@ final class AclTransformation { } } - /** - * Returns the pivot point in the list between the access entries and the - * default entries. This is the index of the first element in the list that is - * a default entry. - * - * @param aclBuilder ArrayList containing entries to build - * @return int pivot point, or -1 if list contains no default entries - */ - private static int calculatePivotOnDefaultEntries(List aclBuilder) { - for (int i = 0; i < aclBuilder.size(); ++i) { - if (aclBuilder.get(i).getScope() == DEFAULT) { - return i; - } - } - return PIVOT_NOT_FOUND; - } - /** * Adds unspecified default entries by copying permissions from the * corresponding access entries. @@ -404,11 +391,10 @@ final class AclTransformation { */ private static void copyDefaultsIfNeeded(List aclBuilder) { Collections.sort(aclBuilder, ACL_ENTRY_COMPARATOR); - int pivot = calculatePivotOnDefaultEntries(aclBuilder); - if (pivot != PIVOT_NOT_FOUND) { - List accessEntries = aclBuilder.subList(0, pivot); - List defaultEntries = aclBuilder.subList(pivot, - aclBuilder.size()); + ScopedAclEntries scopedEntries = new ScopedAclEntries(aclBuilder); + if (!scopedEntries.getDefaultEntries().isEmpty()) { + List accessEntries = scopedEntries.getAccessEntries(); + List defaultEntries = scopedEntries.getDefaultEntries(); List copiedEntries = Lists.newArrayListWithCapacity(3); for (AclEntryType type: EnumSet.of(USER, GROUP, OTHER)) { AclEntry defaultEntryKey = new AclEntry.Builder().setScope(DEFAULT) diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java index 6498fe3c59e..29f07335a65 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSDirectory.java @@ -1151,7 +1151,18 @@ public class FSDirectory implements Closeable { if (inode == null) { throw new FileNotFoundException("File does not exist: " + src); } - inode.setPermission(permissions, inodesInPath.getLatestSnapshotId()); + int snapshotId = inodesInPath.getLatestSnapshotId(); + FsPermission oldPerm = inode.getPermissionStatus(snapshotId).getPermission(); + // This method cannot toggle the ACL bit. + if (oldPerm.getAclBit() != permissions.getAclBit()) { + permissions = new FsPermission( + permissions.getUserAction(), + permissions.getGroupAction(), + permissions.getOtherAction(), + permissions.getStickyBit(), + oldPerm.getAclBit()); + } + inode.setPermission(permissions, snapshotId); } void setOwner(String src, String username, String groupname) @@ -2631,6 +2642,78 @@ public class FSDirectory implements Closeable { return addINode(path, symlink) ? symlink : null; } + void modifyAclEntries(String src, List aclSpec) throws IOException { + writeLock(); + try { + List newAcl = unprotectedModifyAclEntries(src, aclSpec); + fsImage.getEditLog().logSetAcl(src, newAcl); + } finally { + writeUnlock(); + } + } + + private List unprotectedModifyAclEntries(String src, + List aclSpec) throws IOException { + assert hasWriteLock(); + INodesInPath iip = rootDir.getINodesInPath4Write(normalizePath(src), true); + INodeWithAdditionalFields inode = resolveINodeWithAdditionalFields(src, iip); + int snapshotId = iip.getLatestSnapshotId(); + List existingAcl = AclStorage.readINodeLogicalAcl(inode, + snapshotId); + List newAcl = AclTransformation.mergeAclEntries(existingAcl, + aclSpec); + AclStorage.updateINodeAcl(inode, newAcl, snapshotId); + return newAcl; + } + + void removeAclEntries(String src, List aclSpec) throws IOException { + writeLock(); + try { + List newAcl = unprotectedRemoveAclEntries(src, aclSpec); + fsImage.getEditLog().logSetAcl(src, newAcl); + } finally { + writeUnlock(); + } + } + + private List unprotectedRemoveAclEntries(String src, + List aclSpec) throws IOException { + assert hasWriteLock(); + INodesInPath iip = rootDir.getINodesInPath4Write(normalizePath(src), true); + INodeWithAdditionalFields inode = resolveINodeWithAdditionalFields(src, iip); + int snapshotId = iip.getLatestSnapshotId(); + List existingAcl = AclStorage.readINodeLogicalAcl(inode, + snapshotId); + List newAcl = AclTransformation.filterAclEntriesByAclSpec( + existingAcl, aclSpec); + AclStorage.updateINodeAcl(inode, newAcl, snapshotId); + return newAcl; + } + + void removeDefaultAcl(String src) throws IOException { + writeLock(); + try { + List newAcl = unprotectedRemoveDefaultAcl(src); + fsImage.getEditLog().logSetAcl(src, newAcl); + } finally { + writeUnlock(); + } + } + + private List unprotectedRemoveDefaultAcl(String src) + throws IOException { + assert hasWriteLock(); + INodesInPath iip = rootDir.getINodesInPath4Write(normalizePath(src), true); + INodeWithAdditionalFields inode = resolveINodeWithAdditionalFields(src, iip); + int snapshotId = iip.getLatestSnapshotId(); + List existingAcl = AclStorage.readINodeLogicalAcl(inode, + snapshotId); + List newAcl = AclTransformation.filterDefaultAclEntries( + existingAcl); + AclStorage.updateINodeAcl(inode, newAcl, snapshotId); + return newAcl; + } + void removeAcl(String src) throws IOException { writeLock(); try { @@ -2641,74 +2724,67 @@ public class FSDirectory implements Closeable { } } - private void unprotectedRemoveAcl(String src) throws UnresolvedLinkException, - SnapshotAccessControlException, FileNotFoundException { + private void unprotectedRemoveAcl(String src) throws IOException { assert hasWriteLock(); - final INodeWithAdditionalFields node = resolveINodeWithAdditionalField(src); - AclFeature f = node.getAclFeature(); - if (f != null) - node.removeAclFeature(); + INodesInPath iip = rootDir.getINodesInPath4Write(normalizePath(src), true); + INodeWithAdditionalFields inode = resolveINodeWithAdditionalFields(src, iip); + int snapshotId = iip.getLatestSnapshotId(); + AclStorage.removeINodeAcl(inode, snapshotId); } void setAcl(String src, List aclSpec) throws IOException { writeLock(); try { - unprotectedSetAcl(src, aclSpec); - fsImage.getEditLog().logSetAcl(src, aclSpec); + List newAcl = unprotectedSetAcl(src, aclSpec); + fsImage.getEditLog().logSetAcl(src, newAcl); } finally { writeUnlock(); } } - void unprotectedSetAcl(String src, List aclSpec) - throws UnresolvedLinkException, SnapshotAccessControlException, - FileNotFoundException { + List unprotectedSetAcl(String src, List aclSpec) + throws IOException { + // ACL removal is logged to edits as OP_SET_ACL with an empty list. + if (aclSpec.isEmpty()) { + unprotectedRemoveAcl(src); + return AclFeature.EMPTY_ENTRY_LIST; + } + assert hasWriteLock(); - final INodeWithAdditionalFields node = resolveINodeWithAdditionalField(src); - AclFeature f = node.getAclFeature(); - - if (aclSpec.size() == 0) { - if (f != null) - node.removeAclFeature(); - return; - } - - if (f == null) { - f = new AclFeature(); - node.addAclFeature(f); - } - f.setEntries(aclSpec); + INodesInPath iip = rootDir.getINodesInPath4Write(normalizePath(src), true); + INodeWithAdditionalFields inode = resolveINodeWithAdditionalFields(src, iip); + int snapshotId = iip.getLatestSnapshotId(); + List existingAcl = AclStorage.readINodeLogicalAcl(inode, + snapshotId); + List newAcl = AclTransformation.replaceAclEntries(existingAcl, + aclSpec); + AclStorage.updateINodeAcl(inode, newAcl, snapshotId); + return newAcl; } AclStatus getAclStatus(String src) throws IOException { readLock(); try { - final INodeWithAdditionalFields node = resolveINodeWithAdditionalField(src); - AclFeature f = node.getAclFeature(); - - AclStatus.Builder builder = new AclStatus.Builder() - .owner(node.getUserName()).group(node.getGroupName()) - .stickyBit(node.getFsPermission().getStickyBit()); - if (f != null) { - builder.addEntries(f.getEntries()); - } - return builder.build(); + INodesInPath iip = rootDir.getINodesInPath4Write(normalizePath(src), true); + final INodeWithAdditionalFields inode = resolveINodeWithAdditionalFields( + src, iip); + int snapshotId = iip.getLatestSnapshotId(); + List acl = AclStorage.readINodeAcl(inode, snapshotId); + return new AclStatus.Builder() + .owner(inode.getUserName()).group(inode.getGroupName()) + .stickyBit(inode.getFsPermission(snapshotId).getStickyBit()) + .addEntries(acl).build(); } finally { readUnlock(); } } - private INodeWithAdditionalFields resolveINodeWithAdditionalField(String src) - throws UnresolvedLinkException, SnapshotAccessControlException, - FileNotFoundException { - String srcs = normalizePath(src); - final INodesInPath iip = rootDir.getINodesInPath4Write(srcs, true); + private static INodeWithAdditionalFields resolveINodeWithAdditionalFields( + String src, INodesInPath iip) throws FileNotFoundException { INode inode = iip.getLastINode(); if (!(inode instanceof INodeWithAdditionalFields)) throw new FileNotFoundException("cannot find " + src); - - final INodeWithAdditionalFields node = (INodeWithAdditionalFields) inode; - return node; + return (INodeWithAdditionalFields)inode; } /** diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java index a47db9abffe..22c70cf55e2 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/FSNamesystem.java @@ -7326,36 +7326,88 @@ public class FSNamesystem implements Namesystem, FSClusterStats, return results; } - void modifyAclEntries(String src, Iterable aclSpec) { - throw new UnsupportedOperationException("Unimplemented"); - } - - void removeAclEntries(String src, Iterable aclSpec) { - throw new UnsupportedOperationException("Unimplemented"); - } - - void removeDefaultAcl(String src) { - throw new UnsupportedOperationException("Unimplemented"); - } - - void removeAcl(String src) throws IOException { + void modifyAclEntries(String src, List aclSpec) throws IOException { + FSPermissionChecker pc = getPermissionChecker(); checkOperation(OperationCategory.WRITE); + byte[][] pathComponents = FSDirectory.getPathComponentsForReservedPath(src); writeLock(); try { checkOperation(OperationCategory.WRITE); - checkNameNodeSafeMode("Cannot remove acl on " + src); + checkNameNodeSafeMode("Cannot modify ACL entries on " + src); + src = FSDirectory.resolvePath(src, pathComponents, dir); + checkOwner(pc, src); + dir.modifyAclEntries(src, aclSpec); + } finally { + writeUnlock(); + } + getEditLog().logSync(); + logAuditEvent(true, "modifyAclEntries", src); + } + + void removeAclEntries(String src, List aclSpec) throws IOException { + FSPermissionChecker pc = getPermissionChecker(); + checkOperation(OperationCategory.WRITE); + byte[][] pathComponents = FSDirectory.getPathComponentsForReservedPath(src); + writeLock(); + try { + checkOperation(OperationCategory.WRITE); + checkNameNodeSafeMode("Cannot remove ACL entries on " + src); + src = FSDirectory.resolvePath(src, pathComponents, dir); + checkOwner(pc, src); + dir.removeAclEntries(src, aclSpec); + } finally { + writeUnlock(); + } + getEditLog().logSync(); + logAuditEvent(true, "removeAclEntries", src); + } + + void removeDefaultAcl(String src) throws IOException { + FSPermissionChecker pc = getPermissionChecker(); + checkOperation(OperationCategory.WRITE); + byte[][] pathComponents = FSDirectory.getPathComponentsForReservedPath(src); + writeLock(); + try { + checkOperation(OperationCategory.WRITE); + checkNameNodeSafeMode("Cannot remove default ACL entries on " + src); + src = FSDirectory.resolvePath(src, pathComponents, dir); + checkOwner(pc, src); + dir.removeDefaultAcl(src); + } finally { + writeUnlock(); + } + getEditLog().logSync(); + logAuditEvent(true, "removeDefaultAcl", src); + } + + void removeAcl(String src) throws IOException { + FSPermissionChecker pc = getPermissionChecker(); + checkOperation(OperationCategory.WRITE); + byte[][] pathComponents = FSDirectory.getPathComponentsForReservedPath(src); + writeLock(); + try { + checkOperation(OperationCategory.WRITE); + checkNameNodeSafeMode("Cannot remove ACL on " + src); + src = FSDirectory.resolvePath(src, pathComponents, dir); + checkOwner(pc, src); dir.removeAcl(src); } finally { writeUnlock(); } + getEditLog().logSync(); + logAuditEvent(true, "removeAcl", src); } void setAcl(String src, List aclSpec) throws IOException { + FSPermissionChecker pc = getPermissionChecker(); checkOperation(OperationCategory.WRITE); + byte[][] pathComponents = FSDirectory.getPathComponentsForReservedPath(src); writeLock(); try { checkOperation(OperationCategory.WRITE); - checkNameNodeSafeMode("Cannot set acl on " + src); + checkNameNodeSafeMode("Cannot set ACL on " + src); + src = FSDirectory.resolvePath(src, pathComponents, dir); + checkOwner(pc, src); dir.setAcl(src, aclSpec); } finally { writeUnlock(); diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ScopedAclEntries.java b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ScopedAclEntries.java new file mode 100644 index 00000000000..4d7f06177bd --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/main/java/org/apache/hadoop/hdfs/server/namenode/ScopedAclEntries.java @@ -0,0 +1,93 @@ +/** + * 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.hdfs.server.namenode; + +import java.util.Collections; +import java.util.List; + +import org.apache.hadoop.classification.InterfaceAudience; +import org.apache.hadoop.fs.permission.AclEntry; +import org.apache.hadoop.fs.permission.AclEntryScope; + +/** + * Groups a list of ACL entries into separate lists for access entries vs. + * default entries. + */ +@InterfaceAudience.Private +final class ScopedAclEntries { + private static final int PIVOT_NOT_FOUND = -1; + + private final List accessEntries; + private final List defaultEntries; + + /** + * Creates a new ScopedAclEntries from the given list. It is assumed that the + * list is already sorted such that all access entries precede all default + * entries. + * + * @param aclEntries List to separate + */ + public ScopedAclEntries(List aclEntries) { + int pivot = calculatePivotOnDefaultEntries(aclEntries); + if (pivot != PIVOT_NOT_FOUND) { + accessEntries = pivot != 0 ? aclEntries.subList(0, pivot) : + Collections.emptyList(); + defaultEntries = aclEntries.subList(pivot, aclEntries.size()); + } else { + accessEntries = !aclEntries.isEmpty() ? aclEntries : null; + defaultEntries = Collections.emptyList(); + } + } + + /** + * Returns access entries. + * + * @return List containing just access entries, or an empty list if + * there are no access entries + */ + public List getAccessEntries() { + return accessEntries; + } + + /** + * Returns default entries. + * + * @return List containing just default entries, or an empty list if + * there are no default entries + */ + public List getDefaultEntries() { + return defaultEntries; + } + + /** + * Returns the pivot point in the list between the access entries and the + * default entries. This is the index of the first element in the list that is + * a default entry. + * + * @param aclBuilder ArrayList containing entries to build + * @return int pivot point, or -1 if list contains no default entries + */ + private static int calculatePivotOnDefaultEntries(List aclBuilder) { + for (int i = 0; i < aclBuilder.size(); ++i) { + if (aclBuilder.get(i).getScope() == AclEntryScope.DEFAULT) { + return i; + } + } + return PIVOT_NOT_FOUND; + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSafeMode.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSafeMode.java index 7aaff5a04ee..4958b1dd1b5 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSafeMode.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/TestSafeMode.java @@ -31,6 +31,7 @@ import org.apache.hadoop.fs.FSDataOutputStream; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; +import org.apache.hadoop.fs.permission.AclEntry; import org.apache.hadoop.fs.permission.FsPermission; import org.apache.hadoop.hdfs.MiniDFSCluster.DataNodeProperties; import org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction; @@ -328,12 +329,48 @@ public class TestSafeMode { fs.setTimes(file1, 0, 0); }}); + runFsFun("modifyAclEntries while in SM", new FSRun() { + @Override + public void run(FileSystem fs) throws IOException { + fs.modifyAclEntries(file1, Lists.newArrayList()); + }}); + + runFsFun("removeAclEntries while in SM", new FSRun() { + @Override + public void run(FileSystem fs) throws IOException { + fs.removeAclEntries(file1, Lists.newArrayList()); + }}); + + runFsFun("removeDefaultAcl while in SM", new FSRun() { + @Override + public void run(FileSystem fs) throws IOException { + fs.removeDefaultAcl(file1); + }}); + + runFsFun("removeAcl while in SM", new FSRun() { + @Override + public void run(FileSystem fs) throws IOException { + fs.removeAcl(file1); + }}); + + runFsFun("setAcl while in SM", new FSRun() { + @Override + public void run(FileSystem fs) throws IOException { + fs.setAcl(file1, Lists.newArrayList()); + }}); + try { DFSTestUtil.readFile(fs, file1); } catch (IOException ioe) { fail("Set times failed while in SM"); } + try { + fs.getAclStatus(file1); + } catch (IOException ioe) { + fail("getAclStatus failed while in SM"); + } + assertFalse("Could not leave SM", dfs.setSafeMode(SafeModeAction.SAFEMODE_LEAVE)); } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/AclTestHelpers.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/AclTestHelpers.java new file mode 100644 index 00000000000..def5fc1fb27 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/AclTestHelpers.java @@ -0,0 +1,96 @@ +/** + * 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.hdfs.server.namenode; + +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.FsAction; + +/** + * Helper methods useful for writing ACL tests. + */ +final class AclTestHelpers { + + /** + * Create a new AclEntry with scope, type and permission (no name). + * + * @param scope AclEntryScope scope of the ACL entry + * @param type AclEntryType ACL entry type + * @param permission FsAction set of permissions in the ACL entry + * @return AclEntry new AclEntry + */ + public static AclEntry aclEntry(AclEntryScope scope, AclEntryType type, + FsAction permission) { + return new AclEntry.Builder() + .setScope(scope) + .setType(type) + .setPermission(permission) + .build(); + } + + /** + * Create a new AclEntry with scope, type, name and permission. + * + * @param scope AclEntryScope scope of the ACL entry + * @param type AclEntryType ACL entry type + * @param name String optional ACL entry name + * @param permission FsAction set of permissions in the ACL entry + * @return AclEntry new AclEntry + */ + public static AclEntry aclEntry(AclEntryScope scope, AclEntryType type, + String name, FsAction permission) { + return new AclEntry.Builder() + .setScope(scope) + .setType(type) + .setName(name) + .setPermission(permission) + .build(); + } + + /** + * Create a new AclEntry with scope, type and name (no permission). + * + * @param scope AclEntryScope scope of the ACL entry + * @param type AclEntryType ACL entry type + * @param name String optional ACL entry name + * @return AclEntry new AclEntry + */ + public static AclEntry aclEntry(AclEntryScope scope, AclEntryType type, + String name) { + return new AclEntry.Builder() + .setScope(scope) + .setType(type) + .setName(name) + .build(); + } + + /** + * Create a new AclEntry with scope and type (no name or permission). + * + * @param scope AclEntryScope scope of the ACL entry + * @param type AclEntryType ACL entry type + * @return AclEntry new AclEntry + */ + public static AclEntry aclEntry(AclEntryScope scope, AclEntryType type) { + return new AclEntry.Builder() + .setScope(scope) + .setType(type) + .build(); + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAclTransformation.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAclTransformation.java index cc9e201e393..b646c672a01 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAclTransformation.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestAclTransformation.java @@ -20,6 +20,7 @@ package org.apache.hadoop.hdfs.server.namenode; import static org.apache.hadoop.fs.permission.AclEntryScope.*; import static org.apache.hadoop.fs.permission.AclEntryType.*; import static org.apache.hadoop.fs.permission.FsAction.*; +import static org.apache.hadoop.hdfs.server.namenode.AclTestHelpers.*; import static org.apache.hadoop.hdfs.server.namenode.AclTransformation.*; import static org.junit.Assert.*; @@ -1204,39 +1205,4 @@ public class TestAclTransformation { aclEntry(ACCESS, MASK, ALL)); replaceAclEntries(existing, aclSpec); } - - private static AclEntry aclEntry(AclEntryScope scope, AclEntryType type, - FsAction permission) { - return new AclEntry.Builder() - .setScope(scope) - .setType(type) - .setPermission(permission) - .build(); - } - - private static AclEntry aclEntry(AclEntryScope scope, AclEntryType type, - String name, FsAction permission) { - return new AclEntry.Builder() - .setScope(scope) - .setType(type) - .setName(name) - .setPermission(permission) - .build(); - } - - private static AclEntry aclEntry(AclEntryScope scope, AclEntryType type, - String name) { - return new AclEntry.Builder() - .setScope(scope) - .setType(type) - .setName(name) - .build(); - } - - private static AclEntry aclEntry(AclEntryScope scope, AclEntryType type) { - return new AclEntry.Builder() - .setScope(scope) - .setType(type) - .build(); - } } diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImageWithAcl.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImageWithAcl.java index ee9940fcfe4..a36d02879de 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImageWithAcl.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSImageWithAcl.java @@ -17,15 +17,17 @@ */ package org.apache.hadoop.hdfs.server.namenode; +import static org.apache.hadoop.hdfs.server.namenode.AclTestHelpers.*; +import static org.apache.hadoop.fs.permission.AclEntryScope.*; +import static org.apache.hadoop.fs.permission.AclEntryType.*; +import static org.apache.hadoop.fs.permission.FsAction.*; + import java.io.IOException; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.Path; 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.hdfs.DistributedFileSystem; import org.apache.hadoop.hdfs.MiniDFSCluster; import org.apache.hadoop.hdfs.protocol.HdfsConstants.SafeModeAction; @@ -59,9 +61,8 @@ public class TestFSImageWithAcl { fs.mkdirs(new Path("/23")); AclEntry e = new AclEntry.Builder().setName("foo") - .setPermission(FsAction.READ_EXECUTE).setScope(AclEntryScope.DEFAULT) - .setType(AclEntryType.OTHER).build(); - fs.setAcl(p, Lists.newArrayList(e)); + .setPermission(READ_EXECUTE).setScope(ACCESS).setType(USER).build(); + fs.modifyAclEntries(p, Lists.newArrayList(e)); if (persistNamespace) { fs.setSafeMode(SafeModeAction.SAFEMODE_ENTER); @@ -75,7 +76,24 @@ public class TestFSImageWithAcl { AclStatus s = cluster.getNamesystem().getAclStatus(p.toString()); AclEntry[] returned = Lists.newArrayList(s.getEntries()).toArray( new AclEntry[0]); - Assert.assertArrayEquals(new AclEntry[] { e }, returned); + Assert.assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", READ_EXECUTE), + aclEntry(ACCESS, GROUP, READ) }, returned); + + fs.removeAcl(p); + + if (persistNamespace) { + fs.setSafeMode(SafeModeAction.SAFEMODE_ENTER); + fs.saveNamespace(); + fs.setSafeMode(SafeModeAction.SAFEMODE_LEAVE); + } + + cluster.restartNameNode(); + cluster.waitActive(); + + s = cluster.getNamesystem().getAclStatus(p.toString()); + returned = Lists.newArrayList(s.getEntries()).toArray(new AclEntry[0]); + Assert.assertArrayEquals(new AclEntry[] { }, returned); } @Test diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSPermissionChecker.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSPermissionChecker.java new file mode 100644 index 00000000000..fd812794623 --- /dev/null +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestFSPermissionChecker.java @@ -0,0 +1,417 @@ +/** + * 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.hdfs.server.namenode; + +import static org.apache.hadoop.fs.permission.AclEntryScope.*; +import static org.apache.hadoop.fs.permission.AclEntryType.*; +import static org.apache.hadoop.fs.permission.FsAction.*; +import static org.apache.hadoop.hdfs.server.namenode.AclTestHelpers.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.util.Arrays; + +import org.junit.Before; +import org.junit.Test; + +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.FsAction; +import org.apache.hadoop.fs.permission.FsPermission; +import org.apache.hadoop.fs.permission.PermissionStatus; +import org.apache.hadoop.hdfs.server.namenode.snapshot.Snapshot; +import org.apache.hadoop.security.AccessControlException; +import org.apache.hadoop.security.UserGroupInformation; + +/** + * Unit tests covering FSPermissionChecker. All tests in this suite have been + * cross-validated against Linux setfacl/getfacl to check for consistency of the + * HDFS implementation. + */ +public class TestFSPermissionChecker { + private static final long PREFERRED_BLOCK_SIZE = 128 * 1024 * 1024; + private static final short REPLICATION = 3; + private static final String SUPERGROUP = "supergroup"; + private static final String SUPERUSER = "superuser"; + private static final UserGroupInformation BRUCE = + UserGroupInformation.createUserForTesting("bruce", new String[] { }); + private static final UserGroupInformation DIANA = + UserGroupInformation.createUserForTesting("diana", new String[] { "sales" }); + private static final UserGroupInformation CLARK = + UserGroupInformation.createUserForTesting("clark", new String[] { "execs" }); + + private INodeDirectory inodeRoot; + + @Before + public void setUp() { + PermissionStatus permStatus = PermissionStatus.createImmutable(SUPERUSER, + SUPERGROUP, FsPermission.createImmutable((short)0755)); + inodeRoot = new INodeDirectory(INodeId.ROOT_INODE_ID, + INodeDirectory.ROOT_NAME, permStatus, 0L); + } + + @Test + public void testAclOwner() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "execs", + (short)0640); + addAcl(inodeFile, + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, MASK, READ), + aclEntry(ACCESS, OTHER, NONE)); + assertPermissionGranted(BRUCE, "/file1", READ); + assertPermissionGranted(BRUCE, "/file1", WRITE); + assertPermissionGranted(BRUCE, "/file1", READ_WRITE); + assertPermissionDenied(BRUCE, "/file1", EXECUTE); + assertPermissionDenied(DIANA, "/file1", READ); + assertPermissionDenied(DIANA, "/file1", WRITE); + assertPermissionDenied(DIANA, "/file1", EXECUTE); + } + + @Test + public void testAclNamedUser() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "execs", + (short)0640); + addAcl(inodeFile, + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, USER, "diana", READ), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, MASK, READ), + aclEntry(ACCESS, OTHER, NONE)); + assertPermissionGranted(DIANA, "/file1", READ); + assertPermissionDenied(DIANA, "/file1", WRITE); + assertPermissionDenied(DIANA, "/file1", EXECUTE); + assertPermissionDenied(DIANA, "/file1", READ_WRITE); + assertPermissionDenied(DIANA, "/file1", READ_EXECUTE); + assertPermissionDenied(DIANA, "/file1", WRITE_EXECUTE); + assertPermissionDenied(DIANA, "/file1", ALL); + } + + @Test + public void testAclNamedUserDeny() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "execs", + (short)0644); + addAcl(inodeFile, + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, USER, "diana", NONE), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, MASK, READ), + aclEntry(ACCESS, OTHER, READ)); + assertPermissionGranted(BRUCE, "/file1", READ_WRITE); + assertPermissionGranted(CLARK, "/file1", READ); + assertPermissionDenied(DIANA, "/file1", READ); + } + + @Test + public void testAclNamedUserTraverseDeny() throws IOException { + INodeDirectory inodeDir = createINodeDirectory(inodeRoot, "dir1", "bruce", + "execs", (short)0755); + INodeFile inodeFile = createINodeFile(inodeDir, "file1", "bruce", "execs", + (short)0644); + addAcl(inodeDir, + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "diana", NONE), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, MASK, READ_EXECUTE), + aclEntry(ACCESS, OTHER, READ_EXECUTE)); + assertPermissionGranted(BRUCE, "/dir1/file1", READ_WRITE); + assertPermissionGranted(CLARK, "/dir1/file1", READ); + assertPermissionDenied(DIANA, "/dir1/file1", READ); + assertPermissionDenied(DIANA, "/dir1/file1", WRITE); + assertPermissionDenied(DIANA, "/dir1/file1", EXECUTE); + assertPermissionDenied(DIANA, "/dir1/file1", READ_WRITE); + assertPermissionDenied(DIANA, "/dir1/file1", READ_EXECUTE); + assertPermissionDenied(DIANA, "/dir1/file1", WRITE_EXECUTE); + assertPermissionDenied(DIANA, "/dir1/file1", ALL); + } + + @Test + public void testAclNamedUserMask() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "execs", + (short)0620); + addAcl(inodeFile, + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, USER, "diana", READ), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, MASK, WRITE), + aclEntry(ACCESS, OTHER, NONE)); + assertPermissionDenied(DIANA, "/file1", READ); + assertPermissionDenied(DIANA, "/file1", WRITE); + assertPermissionDenied(DIANA, "/file1", EXECUTE); + assertPermissionDenied(DIANA, "/file1", READ_WRITE); + assertPermissionDenied(DIANA, "/file1", READ_EXECUTE); + assertPermissionDenied(DIANA, "/file1", WRITE_EXECUTE); + assertPermissionDenied(DIANA, "/file1", ALL); + } + + @Test + public void testAclGroup() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "execs", + (short)0640); + addAcl(inodeFile, + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, MASK, READ), + aclEntry(ACCESS, OTHER, NONE)); + assertPermissionGranted(CLARK, "/file1", READ); + assertPermissionDenied(CLARK, "/file1", WRITE); + assertPermissionDenied(CLARK, "/file1", EXECUTE); + assertPermissionDenied(CLARK, "/file1", READ_WRITE); + assertPermissionDenied(CLARK, "/file1", READ_EXECUTE); + assertPermissionDenied(CLARK, "/file1", WRITE_EXECUTE); + assertPermissionDenied(CLARK, "/file1", ALL); + } + + @Test + public void testAclGroupDeny() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "sales", + (short)0604); + addAcl(inodeFile, + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, GROUP, NONE), + aclEntry(ACCESS, MASK, NONE), + aclEntry(ACCESS, OTHER, READ)); + assertPermissionGranted(BRUCE, "/file1", READ_WRITE); + assertPermissionGranted(CLARK, "/file1", READ); + assertPermissionDenied(DIANA, "/file1", READ); + assertPermissionDenied(DIANA, "/file1", WRITE); + assertPermissionDenied(DIANA, "/file1", EXECUTE); + assertPermissionDenied(DIANA, "/file1", READ_WRITE); + assertPermissionDenied(DIANA, "/file1", READ_EXECUTE); + assertPermissionDenied(DIANA, "/file1", WRITE_EXECUTE); + assertPermissionDenied(DIANA, "/file1", ALL); + } + + @Test + public void testAclGroupTraverseDeny() throws IOException { + INodeDirectory inodeDir = createINodeDirectory(inodeRoot, "dir1", "bruce", + "execs", (short)0755); + INodeFile inodeFile = createINodeFile(inodeDir, "file1", "bruce", "execs", + (short)0644); + addAcl(inodeDir, + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, GROUP, NONE), + aclEntry(ACCESS, MASK, NONE), + aclEntry(ACCESS, OTHER, READ_EXECUTE)); + assertPermissionGranted(BRUCE, "/dir1/file1", READ_WRITE); + assertPermissionGranted(DIANA, "/dir1/file1", READ); + assertPermissionDenied(CLARK, "/dir1/file1", READ); + assertPermissionDenied(CLARK, "/dir1/file1", WRITE); + assertPermissionDenied(CLARK, "/dir1/file1", EXECUTE); + assertPermissionDenied(CLARK, "/dir1/file1", READ_WRITE); + assertPermissionDenied(CLARK, "/dir1/file1", READ_EXECUTE); + assertPermissionDenied(CLARK, "/dir1/file1", WRITE_EXECUTE); + assertPermissionDenied(CLARK, "/dir1/file1", ALL); + } + + @Test + public void testAclGroupTraverseDenyOnlyDefaultEntries() throws IOException { + INodeDirectory inodeDir = createINodeDirectory(inodeRoot, "dir1", "bruce", + "execs", (short)0755); + INodeFile inodeFile = createINodeFile(inodeDir, "file1", "bruce", "execs", + (short)0644); + addAcl(inodeDir, + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, GROUP, NONE), + aclEntry(ACCESS, OTHER, READ_EXECUTE), + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, GROUP, "sales", NONE), + aclEntry(DEFAULT, GROUP, NONE), + aclEntry(DEFAULT, OTHER, READ_EXECUTE)); + assertPermissionGranted(BRUCE, "/dir1/file1", READ_WRITE); + assertPermissionGranted(DIANA, "/dir1/file1", READ); + assertPermissionDenied(CLARK, "/dir1/file1", READ); + assertPermissionDenied(CLARK, "/dir1/file1", WRITE); + assertPermissionDenied(CLARK, "/dir1/file1", EXECUTE); + assertPermissionDenied(CLARK, "/dir1/file1", READ_WRITE); + assertPermissionDenied(CLARK, "/dir1/file1", READ_EXECUTE); + assertPermissionDenied(CLARK, "/dir1/file1", WRITE_EXECUTE); + assertPermissionDenied(CLARK, "/dir1/file1", ALL); + } + + @Test + public void testAclGroupMask() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "execs", + (short)0644); + addAcl(inodeFile, + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, GROUP, READ_WRITE), + aclEntry(ACCESS, MASK, READ), + aclEntry(ACCESS, OTHER, READ)); + assertPermissionGranted(BRUCE, "/file1", READ_WRITE); + assertPermissionGranted(CLARK, "/file1", READ); + assertPermissionDenied(CLARK, "/file1", WRITE); + assertPermissionDenied(CLARK, "/file1", EXECUTE); + assertPermissionDenied(CLARK, "/file1", READ_WRITE); + assertPermissionDenied(CLARK, "/file1", READ_EXECUTE); + assertPermissionDenied(CLARK, "/file1", WRITE_EXECUTE); + assertPermissionDenied(CLARK, "/file1", ALL); + } + + @Test + public void testAclNamedGroup() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "execs", + (short)0640); + addAcl(inodeFile, + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, GROUP, "sales", READ), + aclEntry(ACCESS, MASK, READ), + aclEntry(ACCESS, OTHER, NONE)); + assertPermissionGranted(BRUCE, "/file1", READ_WRITE); + assertPermissionGranted(CLARK, "/file1", READ); + assertPermissionGranted(DIANA, "/file1", READ); + assertPermissionDenied(DIANA, "/file1", WRITE); + assertPermissionDenied(DIANA, "/file1", EXECUTE); + assertPermissionDenied(DIANA, "/file1", READ_WRITE); + assertPermissionDenied(DIANA, "/file1", READ_EXECUTE); + assertPermissionDenied(DIANA, "/file1", ALL); + } + + @Test + public void testAclNamedGroupDeny() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "sales", + (short)0644); + addAcl(inodeFile, + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, GROUP, "execs", NONE), + aclEntry(ACCESS, MASK, READ), + aclEntry(ACCESS, OTHER, READ)); + assertPermissionGranted(BRUCE, "/file1", READ_WRITE); + assertPermissionGranted(DIANA, "/file1", READ); + assertPermissionDenied(CLARK, "/file1", READ); + assertPermissionDenied(CLARK, "/file1", WRITE); + assertPermissionDenied(CLARK, "/file1", EXECUTE); + assertPermissionDenied(CLARK, "/file1", READ_WRITE); + assertPermissionDenied(CLARK, "/file1", READ_EXECUTE); + assertPermissionDenied(CLARK, "/file1", WRITE_EXECUTE); + assertPermissionDenied(CLARK, "/file1", ALL); + } + + @Test + public void testAclNamedGroupTraverseDeny() throws IOException { + INodeDirectory inodeDir = createINodeDirectory(inodeRoot, "dir1", "bruce", + "execs", (short)0755); + INodeFile inodeFile = createINodeFile(inodeDir, "file1", "bruce", "execs", + (short)0644); + addAcl(inodeDir, + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, GROUP, "sales", NONE), + aclEntry(ACCESS, MASK, READ_EXECUTE), + aclEntry(ACCESS, OTHER, READ_EXECUTE)); + assertPermissionGranted(BRUCE, "/dir1/file1", READ_WRITE); + assertPermissionGranted(CLARK, "/dir1/file1", READ); + assertPermissionDenied(DIANA, "/dir1/file1", READ); + assertPermissionDenied(DIANA, "/dir1/file1", WRITE); + assertPermissionDenied(DIANA, "/dir1/file1", EXECUTE); + assertPermissionDenied(DIANA, "/dir1/file1", READ_WRITE); + assertPermissionDenied(DIANA, "/dir1/file1", READ_EXECUTE); + assertPermissionDenied(DIANA, "/dir1/file1", WRITE_EXECUTE); + assertPermissionDenied(DIANA, "/dir1/file1", ALL); + } + + @Test + public void testAclNamedGroupMask() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "execs", + (short)0644); + addAcl(inodeFile, + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, GROUP, "sales", READ_WRITE), + aclEntry(ACCESS, MASK, READ), + aclEntry(ACCESS, OTHER, READ)); + assertPermissionGranted(BRUCE, "/file1", READ_WRITE); + assertPermissionGranted(CLARK, "/file1", READ); + assertPermissionGranted(DIANA, "/file1", READ); + assertPermissionDenied(DIANA, "/file1", WRITE); + assertPermissionDenied(DIANA, "/file1", EXECUTE); + assertPermissionDenied(DIANA, "/file1", READ_WRITE); + assertPermissionDenied(DIANA, "/file1", READ_EXECUTE); + assertPermissionDenied(DIANA, "/file1", WRITE_EXECUTE); + assertPermissionDenied(DIANA, "/file1", ALL); + } + + @Test + public void testAclOther() throws IOException { + INodeFile inodeFile = createINodeFile(inodeRoot, "file1", "bruce", "sales", + (short)0774); + addAcl(inodeFile, + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "diana", ALL), + aclEntry(ACCESS, GROUP, READ_WRITE), + aclEntry(ACCESS, MASK, ALL), + aclEntry(ACCESS, OTHER, READ)); + assertPermissionGranted(BRUCE, "/file1", ALL); + assertPermissionGranted(DIANA, "/file1", ALL); + assertPermissionGranted(CLARK, "/file1", READ); + assertPermissionDenied(CLARK, "/file1", WRITE); + assertPermissionDenied(CLARK, "/file1", EXECUTE); + assertPermissionDenied(CLARK, "/file1", READ_WRITE); + assertPermissionDenied(CLARK, "/file1", READ_EXECUTE); + assertPermissionDenied(CLARK, "/file1", WRITE_EXECUTE); + assertPermissionDenied(CLARK, "/file1", ALL); + } + + private void addAcl(INodeWithAdditionalFields inode, AclEntry... acl) + throws IOException { + AclStorage.updateINodeAcl((INodeWithAdditionalFields)inode, + Arrays.asList(acl), Snapshot.CURRENT_STATE_ID); + } + + private void assertPermissionGranted(UserGroupInformation user, String path, + FsAction access) throws IOException { + new FSPermissionChecker(SUPERUSER, SUPERGROUP, user).checkPermission(path, + inodeRoot, false, null, null, access, null, true); + } + + private void assertPermissionDenied(UserGroupInformation user, String path, + FsAction access) throws IOException { + try { + new FSPermissionChecker(SUPERUSER, SUPERGROUP, user).checkPermission(path, + inodeRoot, false, null, null, access, null, true); + fail("expected AccessControlException for user + " + user + ", path = " + + path + ", access = " + access); + } catch (AccessControlException e) { + // expected + } + } + + private static INodeDirectory createINodeDirectory(INodeDirectory parent, + String name, String owner, String group, short perm) throws IOException { + PermissionStatus permStatus = PermissionStatus.createImmutable(owner, group, + FsPermission.createImmutable(perm)); + INodeDirectory inodeDirectory = new INodeDirectory( + INodeId.GRANDFATHER_INODE_ID, name.getBytes("UTF-8"), permStatus, 0L); + parent.addChild(inodeDirectory); + return inodeDirectory; + } + + private static INodeFile createINodeFile(INodeDirectory parent, String name, + String owner, String group, short perm) throws IOException { + PermissionStatus permStatus = PermissionStatus.createImmutable(owner, group, + FsPermission.createImmutable(perm)); + INodeFile inodeFile = new INodeFile(INodeId.GRANDFATHER_INODE_ID, + name.getBytes("UTF-8"), permStatus, 0L, 0L, null, REPLICATION, + PREFERRED_BLOCK_SIZE); + parent.addChild(inodeFile); + return inodeFile; + } +} diff --git a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeAcl.java b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeAcl.java index 9885e63f845..fe5d236558b 100644 --- a/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeAcl.java +++ b/hadoop-hdfs-project/hadoop-hdfs/src/test/java/org/apache/hadoop/hdfs/server/namenode/TestNameNodeAcl.java @@ -17,54 +17,786 @@ */ package org.apache.hadoop.hdfs.server.namenode; +import static org.apache.hadoop.hdfs.server.namenode.AclTestHelpers.*; +import static org.apache.hadoop.fs.permission.AclEntryScope.*; +import static org.apache.hadoop.fs.permission.AclEntryType.*; +import static org.apache.hadoop.fs.permission.FsAction.*; +import static org.junit.Assert.*; + +import java.io.FileNotFoundException; import java.io.IOException; +import java.util.List; import org.apache.hadoop.conf.Configuration; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; 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; import org.apache.hadoop.hdfs.MiniDFSCluster; +import org.apache.hadoop.hdfs.protocol.AclException; +import org.apache.hadoop.io.IOUtils; import org.junit.AfterClass; -import org.junit.Assert; +import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import com.google.common.collect.Lists; +/** + * Tests NameNode interaction for all ACL modification APIs. This test suite + * also covers interaction of setPermission with inodes that have ACLs. + */ public class TestNameNodeAcl { private static MiniDFSCluster cluster; private static Configuration conf; + private static FileSystem fs; + private static int pathCount = 0; + private static Path path; @BeforeClass - public static void setUp() throws Exception { + public static void init() throws Exception { conf = new Configuration(); cluster = new MiniDFSCluster.Builder(conf).numDataNodes(1).build(); cluster.waitActive(); + fs = cluster.getFileSystem(); } @AfterClass - public static void tearDown() throws Exception { - cluster.shutdown(); + public static void shutdown() throws Exception { + IOUtils.cleanup(null, fs); + if (cluster != null) { + cluster.shutdown(); + } + } + + @Before + public void setUp() { + pathCount += 1; + path = new Path("/p" + pathCount); + } + + @Test + public void testModifyAclEntries() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo", READ_EXECUTE), + aclEntry(DEFAULT, USER, "foo", READ_EXECUTE)); + fs.modifyAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", READ_EXECUTE), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, USER, "foo", READ_EXECUTE), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02750); + assertAclFeature(true); + } + + @Test + public void testModifyAclEntriesOnlyAccess() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo", READ_EXECUTE)); + fs.modifyAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", READ_EXECUTE), + aclEntry(ACCESS, GROUP, READ_EXECUTE) }, returned); + assertPermission((short)02750); + assertAclFeature(true); + } + + @Test + public void testModifyAclEntriesOnlyDefault() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(DEFAULT, USER, "foo", READ_EXECUTE)); + fs.modifyAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, USER, "foo", READ_EXECUTE), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02750); + assertAclFeature(true); + } + + @Test + public void testModifyAclEntriesMinimal() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo", READ_WRITE)); + fs.modifyAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", READ_WRITE), + aclEntry(ACCESS, GROUP, READ) }, returned); + assertPermission((short)02660); + assertAclFeature(true); + } + + @Test + public void testModifyAclEntriesMinimalDefault() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE)); + fs.modifyAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02750); + assertAclFeature(true); + } + + @Test + public void testModifyAclEntriesCustomMask() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, MASK, NONE)); + fs.modifyAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ) }, returned); + assertPermission((short)02600); + assertAclFeature(true); + } + + @Test + public void testModifyAclEntriesStickyBit() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)01750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo", READ_EXECUTE), + aclEntry(DEFAULT, USER, "foo", READ_EXECUTE)); + fs.modifyAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", READ_EXECUTE), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, USER, "foo", READ_EXECUTE), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)03750); + assertAclFeature(true); + } + + @Test(expected=FileNotFoundException.class) + public void testModifyAclEntriesPathNotFound() throws IOException { + // Path has not been created. + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE)); + fs.modifyAclEntries(path, aclSpec); + } + + @Test(expected=AclException.class) + public void testModifyAclEntriesDefaultOnFile() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + List aclSpec = Lists.newArrayList( + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.modifyAclEntries(path, aclSpec); + } + + @Test + public void testRemoveAclEntries() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo"), + aclEntry(DEFAULT, USER, "foo")); + fs.removeAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02750); + assertAclFeature(true); + } + + @Test + public void testRemoveAclEntriesOnlyAccess() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, USER, "bar", READ_WRITE), + aclEntry(ACCESS, GROUP, READ_WRITE), + aclEntry(ACCESS, OTHER, NONE)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo")); + fs.removeAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "bar", READ_WRITE), + aclEntry(ACCESS, GROUP, READ_WRITE) }, returned); + assertPermission((short)02760); + assertAclFeature(true); + } + + @Test + public void testRemoveAclEntriesOnlyDefault() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL), + aclEntry(DEFAULT, USER, "bar", READ_EXECUTE)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(DEFAULT, USER, "foo")); + fs.removeAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, USER, "bar", READ_EXECUTE), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02750); + assertAclFeature(true); + } + + @Test + public void testRemoveAclEntriesMinimal() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0760)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_WRITE), + aclEntry(ACCESS, OTHER, NONE)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo"), + aclEntry(ACCESS, MASK)); + fs.removeAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { }, returned); + assertPermission((short)0760); + assertAclFeature(false); + } + + + @Test + public void testRemoveAclEntriesMinimalDefault() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo"), + aclEntry(ACCESS, MASK), + aclEntry(DEFAULT, USER, "foo"), + aclEntry(DEFAULT, MASK)); + fs.removeAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02750); + assertAclFeature(true); + } + + @Test + public void testRemoveAclEntriesStickyBit() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)01750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo"), + aclEntry(DEFAULT, USER, "foo")); + fs.removeAclEntries(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)03750); + assertAclFeature(true); + } + + @Test(expected=FileNotFoundException.class) + public void testRemoveAclEntriesPathNotFound() throws IOException { + // Path has not been created. + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, "foo")); + fs.removeAclEntries(path, aclSpec); + } + + @Test + public void testRemoveDefaultAcl() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + fs.removeDefaultAcl(path); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE) }, returned); + assertPermission((short)02770); + assertAclFeature(true); + } + + @Test + public void testRemoveDefaultAclOnlyAccess() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE)); + fs.setAcl(path, aclSpec); + fs.removeDefaultAcl(path); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE) }, returned); + assertPermission((short)02770); + assertAclFeature(true); + } + + @Test + public void testRemoveDefaultAclOnlyDefault() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + fs.removeDefaultAcl(path); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { }, returned); + assertPermission((short)0750); + assertAclFeature(false); + } + + @Test + public void testRemoveDefaultAclMinimal() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + fs.removeDefaultAcl(path); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { }, returned); + assertPermission((short)0750); + assertAclFeature(false); + } + + @Test + public void testRemoveDefaultAclStickyBit() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)01750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + fs.removeDefaultAcl(path); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE) }, returned); + assertPermission((short)03770); + assertAclFeature(true); + } + + @Test(expected=FileNotFoundException.class) + public void testRemoveDefaultAclPathNotFound() throws IOException { + // Path has not been created. + fs.removeDefaultAcl(path); + } + + @Test + public void testRemoveAcl() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + fs.removeAcl(path); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { }, returned); + assertPermission((short)0750); + assertAclFeature(false); + } + + @Test + public void testRemoveAclMinimalAcl() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + fs.removeAcl(path); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { }, returned); + assertPermission((short)0640); + assertAclFeature(false); + } + + @Test + public void testRemoveAclStickyBit() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)01750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + fs.removeAcl(path); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { }, returned); + assertPermission((short)01750); + assertAclFeature(false); + } + + @Test(expected=FileNotFoundException.class) + public void testRemoveAclPathNotFound() throws IOException { + // Path has not been created. + fs.removeAcl(path); } @Test public void testSetAcl() throws IOException { - Path p = new Path("/p"); - FileSystem fs = cluster.getFileSystem(); - fs.create(p).close(); - AclEntry e = new AclEntry.Builder().setName("foo") - .setPermission(FsAction.READ_EXECUTE).setScope(AclEntryScope.DEFAULT) - .setType(AclEntryType.OTHER).build(); - fs.setAcl(p, Lists.newArrayList(e)); - AclStatus s = fs.getAclStatus(p); - AclEntry[] returned = Lists.newArrayList(s.getEntries()).toArray( - new AclEntry[0]); - Assert.assertArrayEquals(new AclEntry[] { e }, returned); + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, USER, "foo", ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, ALL), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02770); + assertAclFeature(true); + } + + @Test + public void testSetAclOnlyAccess() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, USER, "foo", READ), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, OTHER, NONE)); + fs.setAcl(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", READ), + aclEntry(ACCESS, GROUP, READ) }, returned); + assertPermission((short)02640); + assertAclFeature(true); + } + + @Test + public void testSetAclOnlyDefault() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, USER, "foo", ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, ALL), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02750); + assertAclFeature(true); + } + + @Test + public void testSetAclMinimal() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0644)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, USER, "foo", READ), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, OTHER, NONE)); + fs.setAcl(path, aclSpec); + aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, OTHER, NONE)); + fs.setAcl(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { }, returned); + assertPermission((short)0640); + assertAclFeature(false); + } + + @Test + public void testSetAclMinimalDefault() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE)); + fs.setAcl(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02750); + assertAclFeature(true); + } + + @Test + public void testSetAclCustomMask() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, USER, "foo", READ), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, MASK, ALL), + aclEntry(ACCESS, OTHER, NONE)); + fs.setAcl(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", READ), + aclEntry(ACCESS, GROUP, READ) }, returned); + assertPermission((short)02670); + assertAclFeature(true); + } + + @Test + public void testSetAclStickyBit() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)01750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, USER, "foo", ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, ALL), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)03770); + assertAclFeature(true); + } + + @Test(expected=FileNotFoundException.class) + public void testSetAclPathNotFound() throws IOException { + // Path has not been created. + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, USER, "foo", READ), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, OTHER, NONE)); + fs.setAcl(path, aclSpec); + } + + @Test(expected=AclException.class) + public void testSetAclDefaultOnFile() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + List aclSpec = Lists.newArrayList( + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + } + + @Test + public void testSetPermission() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + fs.setPermission(path, FsPermission.createImmutable((short)0700)); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, USER, "foo", ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, ALL), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02700); + assertAclFeature(true); + } + + @Test + public void testSetPermissionOnlyAccess() throws IOException { + FileSystem.create(fs, path, FsPermission.createImmutable((short)0640)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, READ_WRITE), + aclEntry(ACCESS, USER, "foo", READ), + aclEntry(ACCESS, GROUP, READ), + aclEntry(ACCESS, OTHER, NONE)); + fs.setAcl(path, aclSpec); + fs.setPermission(path, FsPermission.createImmutable((short)0600)); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(ACCESS, USER, "foo", READ), + aclEntry(ACCESS, GROUP, READ) }, returned); + assertPermission((short)02600); + assertAclFeature(true); + } + + @Test + public void testSetPermissionOnlyDefault() throws IOException { + FileSystem.mkdirs(fs, path, FsPermission.createImmutable((short)0750)); + List aclSpec = Lists.newArrayList( + aclEntry(ACCESS, USER, ALL), + aclEntry(ACCESS, GROUP, READ_EXECUTE), + aclEntry(ACCESS, OTHER, NONE), + aclEntry(DEFAULT, USER, "foo", ALL)); + fs.setAcl(path, aclSpec); + fs.setPermission(path, FsPermission.createImmutable((short)0700)); + AclStatus s = fs.getAclStatus(path); + AclEntry[] returned = s.getEntries().toArray(new AclEntry[0]); + assertArrayEquals(new AclEntry[] { + aclEntry(DEFAULT, USER, ALL), + aclEntry(DEFAULT, USER, "foo", ALL), + aclEntry(DEFAULT, GROUP, READ_EXECUTE), + aclEntry(DEFAULT, MASK, ALL), + aclEntry(DEFAULT, OTHER, NONE) }, returned); + assertPermission((short)02700); + assertAclFeature(true); + } + + /** + * Asserts whether or not the inode for the test path has an AclFeature. + * + * @param expectAclFeature boolean true if an AclFeature must be present, + * false if an AclFeature must not be present + * @throws IOException thrown if there is an I/O error + */ + private static void assertAclFeature(boolean expectAclFeature) + throws IOException { + INode inode = cluster.getNamesystem().getFSDirectory().getRoot() + .getNode(path.toUri().getPath(), false); + assertNotNull(inode); + assertTrue(inode instanceof INodeWithAdditionalFields); + AclFeature aclFeature = ((INodeWithAdditionalFields)inode).getAclFeature(); + if (expectAclFeature) { + assertNotNull(aclFeature); + } else { + assertNull(aclFeature); + } + } + + /** + * Asserts the value of the FsPermission bits on the inode of the test path. + * + * @param perm short expected permission bits + * @throws IOException thrown if there is an I/O error + */ + private static void assertPermission(short perm) throws IOException { + assertEquals(FsPermission.createImmutable(perm), + fs.getFileStatus(path).getPermission()); } }