HDFS-6635. Refactor encryption zone functionality into new EncryptionZoneManager class. (wang)
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/branches/fs-encryption@1608657 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
dda85637df
commit
ceed0f6c46
|
@ -39,6 +39,9 @@ fs-encryption (Unreleased)
|
||||||
|
|
||||||
HDFS-6629. Not able to create symlinks after HDFS-6516 (umamaheswararao)
|
HDFS-6629. Not able to create symlinks after HDFS-6516 (umamaheswararao)
|
||||||
|
|
||||||
|
HDFS-6635. Refactor encryption zone functionality into new
|
||||||
|
EncryptionZoneManager class. (wang)
|
||||||
|
|
||||||
OPTIMIZATIONS
|
OPTIMIZATIONS
|
||||||
|
|
||||||
BUG FIXES
|
BUG FIXES
|
||||||
|
|
|
@ -0,0 +1,180 @@
|
||||||
|
package org.apache.hadoop.hdfs.server.namenode;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.EnumSet;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import com.google.common.base.Preconditions;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import org.apache.hadoop.fs.UnresolvedLinkException;
|
||||||
|
import org.apache.hadoop.fs.XAttr;
|
||||||
|
import org.apache.hadoop.fs.XAttrSetFlag;
|
||||||
|
import org.apache.hadoop.hdfs.XAttrHelper;
|
||||||
|
import org.apache.hadoop.hdfs.protocol.EncryptionZone;
|
||||||
|
import org.apache.hadoop.hdfs.protocol.SnapshotAccessControlException;
|
||||||
|
|
||||||
|
|
||||||
|
import static org.apache.hadoop.hdfs.server.common.HdfsServerConstants
|
||||||
|
.CRYPTO_XATTR_ENCRYPTION_ZONE;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Manages the list of encryption zones in the filesystem. Relies on the
|
||||||
|
* FSDirectory lock for synchronization.
|
||||||
|
*/
|
||||||
|
public class EncryptionZoneManager {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* EncryptionZoneInt is the internal representation of an encryption zone. The
|
||||||
|
* external representation of an EZ is embodied in an EncryptionZone and
|
||||||
|
* contains the EZ's pathname.
|
||||||
|
*/
|
||||||
|
private class EncryptionZoneInt {
|
||||||
|
private final String keyId;
|
||||||
|
private final long inodeId;
|
||||||
|
|
||||||
|
EncryptionZoneInt(long inodeId, String keyId) {
|
||||||
|
this.keyId = keyId;
|
||||||
|
this.inodeId = inodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getKeyId() {
|
||||||
|
return keyId;
|
||||||
|
}
|
||||||
|
|
||||||
|
long getINodeId() {
|
||||||
|
return inodeId;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getFullPathName() {
|
||||||
|
return dir.getInode(inodeId).getFullPathName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final Map<Long, EncryptionZoneInt> encryptionZones;
|
||||||
|
|
||||||
|
private final FSDirectory dir;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Construct a new EncryptionZoneManager.
|
||||||
|
*
|
||||||
|
* @param dir Enclosing FSDirectory
|
||||||
|
*/
|
||||||
|
public EncryptionZoneManager(FSDirectory dir) {
|
||||||
|
this.dir = dir;
|
||||||
|
encryptionZones = new HashMap<Long, EncryptionZoneInt>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add a new encryption zone.
|
||||||
|
*
|
||||||
|
* @param inodeId of the encryption zone
|
||||||
|
* @param keyId encryption zone key id
|
||||||
|
*/
|
||||||
|
void addEncryptionZone(Long inodeId, String keyId) {
|
||||||
|
final EncryptionZoneInt ez = new EncryptionZoneInt(inodeId, keyId);
|
||||||
|
encryptionZones.put(inodeId, ez);
|
||||||
|
}
|
||||||
|
|
||||||
|
void removeEncryptionZone(Long inodeId) {
|
||||||
|
encryptionZones.remove(inodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if an IIP is within an encryption zone.
|
||||||
|
*/
|
||||||
|
boolean isInAnEZ(INodesInPath iip)
|
||||||
|
throws UnresolvedLinkException, SnapshotAccessControlException {
|
||||||
|
return (getEncryptionZoneForPath(iip) != null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EncryptionZoneInt getEncryptionZoneForPath(INodesInPath iip) {
|
||||||
|
Preconditions.checkNotNull(iip);
|
||||||
|
final INode[] inodes = iip.getINodes();
|
||||||
|
for (int i = inodes.length - 1; i >= 0; i--) {
|
||||||
|
final INode inode = inodes[i];
|
||||||
|
if (inode != null) {
|
||||||
|
final EncryptionZoneInt ezi = encryptionZones.get(inode.getId());
|
||||||
|
if (ezi != null) {
|
||||||
|
return ezi;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Throws an exception if the provided inode cannot be renamed into the
|
||||||
|
* destination because of differing encryption zones.
|
||||||
|
*
|
||||||
|
* @param srcIIP source IIP
|
||||||
|
* @param dstIIP destination IIP
|
||||||
|
* @param src source path, used for debugging
|
||||||
|
* @throws IOException if the src cannot be renamed to the dst
|
||||||
|
*/
|
||||||
|
void checkMoveValidity(INodesInPath srcIIP, INodesInPath dstIIP, String src)
|
||||||
|
throws IOException {
|
||||||
|
final boolean srcInEZ = (getEncryptionZoneForPath(srcIIP) != null);
|
||||||
|
final boolean dstInEZ = (getEncryptionZoneForPath(dstIIP) != null);
|
||||||
|
if (srcInEZ) {
|
||||||
|
if (!dstInEZ) {
|
||||||
|
throw new IOException(src + " can't be moved from an encryption zone.");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (dstInEZ) {
|
||||||
|
throw new IOException(src + " can't be moved into an encryption zone.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (srcInEZ || dstInEZ) {
|
||||||
|
final EncryptionZoneInt srcEZI = getEncryptionZoneForPath(srcIIP);
|
||||||
|
final EncryptionZoneInt dstEZI = getEncryptionZoneForPath(dstIIP);
|
||||||
|
Preconditions.checkArgument(srcEZI != null, "couldn't find src EZ?");
|
||||||
|
Preconditions.checkArgument(dstEZI != null, "couldn't find dst EZ?");
|
||||||
|
if (srcEZI != dstEZI) {
|
||||||
|
final String srcEZPath = srcEZI.getFullPathName();
|
||||||
|
final String dstEZPath = dstEZI.getFullPathName();
|
||||||
|
final StringBuilder sb = new StringBuilder(src);
|
||||||
|
sb.append(" can't be moved from encryption zone ");
|
||||||
|
sb.append(srcEZPath);
|
||||||
|
sb.append(" to encryption zone ");
|
||||||
|
sb.append(dstEZPath);
|
||||||
|
sb.append(".");
|
||||||
|
throw new IOException(sb.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
XAttr createEncryptionZone(String src, String keyId) throws IOException {
|
||||||
|
if (dir.isNonEmptyDirectory(src)) {
|
||||||
|
throw new IOException(
|
||||||
|
"Attempt to create an encryption zone for a non-empty directory.");
|
||||||
|
}
|
||||||
|
|
||||||
|
final INodesInPath srcIIP = dir.getINodesInPath4Write(src, false);
|
||||||
|
final EncryptionZoneInt ezi = getEncryptionZoneForPath(srcIIP);
|
||||||
|
if (ezi != null) {
|
||||||
|
throw new IOException("Directory " + src +
|
||||||
|
" is already in an encryption zone. (" + ezi.getFullPathName() + ")");
|
||||||
|
}
|
||||||
|
|
||||||
|
final XAttr keyIdXAttr =
|
||||||
|
XAttrHelper.buildXAttr(CRYPTO_XATTR_ENCRYPTION_ZONE, keyId.getBytes());
|
||||||
|
final List<XAttr> xattrs = Lists.newArrayListWithCapacity(1);
|
||||||
|
xattrs.add(keyIdXAttr);
|
||||||
|
final INode inode =
|
||||||
|
dir.unprotectedSetXAttrs(src, xattrs, EnumSet.of(XAttrSetFlag.CREATE));
|
||||||
|
addEncryptionZone(inode.getId(), keyId);
|
||||||
|
return keyIdXAttr;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<EncryptionZone> listEncryptionZones() throws IOException {
|
||||||
|
final List<EncryptionZone> ret =
|
||||||
|
Lists.newArrayListWithExpectedSize(encryptionZones.size());
|
||||||
|
for (EncryptionZoneInt ezi : encryptionZones.values()) {
|
||||||
|
ret.add(new EncryptionZone(ezi.getFullPathName(), ezi.getKeyId()));
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
|
@ -27,10 +27,8 @@ import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.EnumSet;
|
import java.util.EnumSet;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
|
|
||||||
import com.google.protobuf.InvalidProtocolBufferException;
|
import com.google.protobuf.InvalidProtocolBufferException;
|
||||||
|
@ -139,35 +137,6 @@ public class FSDirectory implements Closeable {
|
||||||
private long yieldCount = 0; // keep track of lock yield count.
|
private long yieldCount = 0; // keep track of lock yield count.
|
||||||
private final int inodeXAttrsLimit; //inode xattrs max limit
|
private final int inodeXAttrsLimit; //inode xattrs max limit
|
||||||
|
|
||||||
/*
|
|
||||||
* EncryptionZoneInt is the internal representation of an encryption
|
|
||||||
* zone. The external representation of an EZ is embodied in an
|
|
||||||
* EncryptionZone and contains the EZ's pathname.
|
|
||||||
*/
|
|
||||||
private class EncryptionZoneInt {
|
|
||||||
private final String keyId;
|
|
||||||
private final long inodeId;
|
|
||||||
|
|
||||||
EncryptionZoneInt(String keyId, long inodeId) {
|
|
||||||
this.keyId = keyId;
|
|
||||||
this.inodeId = inodeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getKeyId() {
|
|
||||||
return keyId;
|
|
||||||
}
|
|
||||||
|
|
||||||
long getINodeId() {
|
|
||||||
return inodeId;
|
|
||||||
}
|
|
||||||
|
|
||||||
String getFullPathName() {
|
|
||||||
return getInode(inodeId).getFullPathName();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private final Map<Long, EncryptionZoneInt> encryptionZones;
|
|
||||||
|
|
||||||
// lock to protect the directory and BlockMap
|
// lock to protect the directory and BlockMap
|
||||||
private final ReentrantReadWriteLock dirLock;
|
private final ReentrantReadWriteLock dirLock;
|
||||||
|
|
||||||
|
@ -204,6 +173,8 @@ public class FSDirectory implements Closeable {
|
||||||
return this.dirLock.getWriteHoldCount();
|
return this.dirLock.getWriteHoldCount();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final EncryptionZoneManager ezManager;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Caches frequently used file names used in {@link INode} to reuse
|
* Caches frequently used file names used in {@link INode} to reuse
|
||||||
* byte[] objects and reduce heap usage.
|
* byte[] objects and reduce heap usage.
|
||||||
|
@ -252,7 +223,8 @@ public class FSDirectory implements Closeable {
|
||||||
+ " times");
|
+ " times");
|
||||||
nameCache = new NameCache<ByteArray>(threshold);
|
nameCache = new NameCache<ByteArray>(threshold);
|
||||||
namesystem = ns;
|
namesystem = ns;
|
||||||
encryptionZones = new HashMap<Long, EncryptionZoneInt>();
|
|
||||||
|
ezManager = new EncryptionZoneManager(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private FSNamesystem getFSNamesystem() {
|
private FSNamesystem getFSNamesystem() {
|
||||||
|
@ -550,7 +522,7 @@ public class FSDirectory implements Closeable {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
checkEncryptionZoneMoveValidity(srcIIP, dstIIP, src);
|
ezManager.checkMoveValidity(srcIIP, dstIIP, src);
|
||||||
// Ensure dst has quota to accommodate rename
|
// Ensure dst has quota to accommodate rename
|
||||||
verifyFsLimitsForRename(srcIIP, dstIIP);
|
verifyFsLimitsForRename(srcIIP, dstIIP);
|
||||||
verifyQuotaForRename(srcIIP.getINodes(), dstIIP.getINodes());
|
verifyQuotaForRename(srcIIP.getINodes(), dstIIP.getINodes());
|
||||||
|
@ -629,7 +601,7 @@ public class FSDirectory implements Closeable {
|
||||||
throw new IOException(error);
|
throw new IOException(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
checkEncryptionZoneMoveValidity(srcIIP, dstIIP, src);
|
ezManager.checkMoveValidity(srcIIP, dstIIP, src);
|
||||||
final INode dstInode = dstIIP.getLastINode();
|
final INode dstInode = dstIIP.getLastINode();
|
||||||
List<INodeDirectorySnapshottable> snapshottableDirs =
|
List<INodeDirectorySnapshottable> snapshottableDirs =
|
||||||
new ArrayList<INodeDirectorySnapshottable>();
|
new ArrayList<INodeDirectorySnapshottable>();
|
||||||
|
@ -937,61 +909,12 @@ public class FSDirectory implements Closeable {
|
||||||
throws UnresolvedLinkException, SnapshotAccessControlException {
|
throws UnresolvedLinkException, SnapshotAccessControlException {
|
||||||
readLock();
|
readLock();
|
||||||
try {
|
try {
|
||||||
return (getEncryptionZoneForPath(iip) != null);
|
return ezManager.isInAnEZ(iip);
|
||||||
} finally {
|
} finally {
|
||||||
readUnlock();
|
readUnlock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private EncryptionZoneInt getEncryptionZoneForPath(INodesInPath iip) {
|
|
||||||
Preconditions.checkNotNull(iip);
|
|
||||||
final INode[] inodes = iip.getINodes();
|
|
||||||
for (int i = inodes.length -1; i >= 0; i--) {
|
|
||||||
final INode inode = inodes[i];
|
|
||||||
if (inode != null) {
|
|
||||||
final EncryptionZoneInt ezi = encryptionZones.get(inode.getId());
|
|
||||||
if (ezi != null) {
|
|
||||||
return ezi;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkEncryptionZoneMoveValidity(INodesInPath srcIIP,
|
|
||||||
INodesInPath dstIIP, String src)
|
|
||||||
throws IOException {
|
|
||||||
final boolean srcInEZ = (getEncryptionZoneForPath(srcIIP) != null);
|
|
||||||
final boolean dstInEZ = (getEncryptionZoneForPath(dstIIP) != null);
|
|
||||||
if (srcInEZ) {
|
|
||||||
if (!dstInEZ) {
|
|
||||||
throw new IOException(src + " can't be moved from an encryption zone.");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (dstInEZ) {
|
|
||||||
throw new IOException(src + " can't be moved into an encryption zone.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (srcInEZ || dstInEZ) {
|
|
||||||
final EncryptionZoneInt srcEZI = getEncryptionZoneForPath(srcIIP);
|
|
||||||
final EncryptionZoneInt dstEZI = getEncryptionZoneForPath(dstIIP);
|
|
||||||
Preconditions.checkArgument(srcEZI != null, "couldn't find src EZ?");
|
|
||||||
Preconditions.checkArgument(dstEZI != null, "couldn't find dst EZ?");
|
|
||||||
if (srcEZI != dstEZI) {
|
|
||||||
final String srcEZPath = srcEZI.getFullPathName();
|
|
||||||
final String dstEZPath = dstEZI.getFullPathName();
|
|
||||||
final StringBuilder sb = new StringBuilder(src);
|
|
||||||
sb.append(" can't be moved from encryption zone ");
|
|
||||||
sb.append(srcEZPath);
|
|
||||||
sb.append(" to encryption zone ");
|
|
||||||
sb.append(dstEZPath);
|
|
||||||
sb.append(".");
|
|
||||||
throw new IOException(sb.toString());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set file replication
|
* Set file replication
|
||||||
*
|
*
|
||||||
|
@ -2157,8 +2080,8 @@ public class FSDirectory implements Closeable {
|
||||||
for (XAttr xattr : xattrs) {
|
for (XAttr xattr : xattrs) {
|
||||||
final String xaName = XAttrHelper.getPrefixName(xattr);
|
final String xaName = XAttrHelper.getPrefixName(xattr);
|
||||||
if (CRYPTO_XATTR_ENCRYPTION_ZONE.equals(xaName)) {
|
if (CRYPTO_XATTR_ENCRYPTION_ZONE.equals(xaName)) {
|
||||||
encryptionZones.put(inode.getId(), new EncryptionZoneInt(
|
ezManager.addEncryptionZone(inode.getId(),
|
||||||
new String(xattr.getValue()), inode.getId()));
|
new String(xattr.getValue()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2174,7 +2097,7 @@ public class FSDirectory implements Closeable {
|
||||||
for (INode inode : inodes) {
|
for (INode inode : inodes) {
|
||||||
if (inode != null && inode instanceof INodeWithAdditionalFields) {
|
if (inode != null && inode instanceof INodeWithAdditionalFields) {
|
||||||
inodeMap.remove(inode);
|
inodeMap.remove(inode);
|
||||||
encryptionZones.remove(inode.getId());
|
ezManager.removeEncryptionZone(inode.getId());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2700,27 +2623,7 @@ public class FSDirectory implements Closeable {
|
||||||
throws IOException {
|
throws IOException {
|
||||||
writeLock();
|
writeLock();
|
||||||
try {
|
try {
|
||||||
if (isNonEmptyDirectory(src)) {
|
return ezManager.createEncryptionZone(src, keyId);
|
||||||
throw new IOException(
|
|
||||||
"Attempt to create an encryption zone for a non-empty directory.");
|
|
||||||
}
|
|
||||||
|
|
||||||
final INodesInPath srcIIP = getINodesInPath4Write(src, false);
|
|
||||||
final EncryptionZoneInt ezi = getEncryptionZoneForPath(srcIIP);
|
|
||||||
if (ezi != null) {
|
|
||||||
throw new IOException("Directory " + src +
|
|
||||||
" is already in an encryption zone. (" + ezi.getFullPathName() + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
final XAttr keyIdXAttr =
|
|
||||||
XAttrHelper.buildXAttr(CRYPTO_XATTR_ENCRYPTION_ZONE, keyId.getBytes());
|
|
||||||
final List<XAttr> xattrs = Lists.newArrayListWithCapacity(1);
|
|
||||||
xattrs.add(keyIdXAttr);
|
|
||||||
final INode inode = unprotectedSetXAttrs(src, xattrs,
|
|
||||||
EnumSet.of(XAttrSetFlag.CREATE));
|
|
||||||
encryptionZones.put(inode.getId(),
|
|
||||||
new EncryptionZoneInt(keyId, inode.getId()));
|
|
||||||
return keyIdXAttr;
|
|
||||||
} finally {
|
} finally {
|
||||||
writeUnlock();
|
writeUnlock();
|
||||||
}
|
}
|
||||||
|
@ -2729,12 +2632,7 @@ public class FSDirectory implements Closeable {
|
||||||
List<EncryptionZone> listEncryptionZones() throws IOException {
|
List<EncryptionZone> listEncryptionZones() throws IOException {
|
||||||
readLock();
|
readLock();
|
||||||
try {
|
try {
|
||||||
final List<EncryptionZone> ret =
|
return ezManager.listEncryptionZones();
|
||||||
Lists.newArrayListWithExpectedSize(encryptionZones.size());
|
|
||||||
for (EncryptionZoneInt ezi : encryptionZones.values()) {
|
|
||||||
ret.add(new EncryptionZone(ezi.getFullPathName(), ezi.getKeyId()));
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
} finally {
|
} finally {
|
||||||
readUnlock();
|
readUnlock();
|
||||||
}
|
}
|
||||||
|
@ -2823,9 +2721,7 @@ public class FSDirectory implements Closeable {
|
||||||
for (XAttr xattr : newXAttrs) {
|
for (XAttr xattr : newXAttrs) {
|
||||||
final String xaName = XAttrHelper.getPrefixName(xattr);
|
final String xaName = XAttrHelper.getPrefixName(xattr);
|
||||||
if (CRYPTO_XATTR_ENCRYPTION_ZONE.equals(xaName)) {
|
if (CRYPTO_XATTR_ENCRYPTION_ZONE.equals(xaName)) {
|
||||||
final EncryptionZoneInt ez =
|
ezManager.addEncryptionZone(inode.getId(), new String(xattr.getValue()));
|
||||||
new EncryptionZoneInt(new String(xattr.getValue()), inode.getId());
|
|
||||||
encryptionZones.put(inode.getId(), ez);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3084,7 +2980,7 @@ public class FSDirectory implements Closeable {
|
||||||
* @throws UnresolvedLinkException if symlink can't be resolved
|
* @throws UnresolvedLinkException if symlink can't be resolved
|
||||||
* @throws SnapshotAccessControlException if path is in RO snapshot
|
* @throws SnapshotAccessControlException if path is in RO snapshot
|
||||||
*/
|
*/
|
||||||
private INodesInPath getINodesInPath4Write(String src, boolean resolveLink)
|
INodesInPath getINodesInPath4Write(String src, boolean resolveLink)
|
||||||
throws UnresolvedLinkException, SnapshotAccessControlException {
|
throws UnresolvedLinkException, SnapshotAccessControlException {
|
||||||
final byte[][] components = INode.getPathComponents(src);
|
final byte[][] components = INode.getPathComponents(src);
|
||||||
INodesInPath inodesInPath = INodesInPath.resolve(rootDir, components,
|
INodesInPath inodesInPath = INodesInPath.resolve(rootDir, components,
|
||||||
|
|
Loading…
Reference in New Issue