diff options
8 files changed, 188 insertions, 1605 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystem.java deleted file mode 100644 index c3634d7bf6..0000000000 --- a/src/main/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystem.java +++ /dev/null @@ -1,143 +0,0 @@ -// Copyright 2014 The Bazel Authors. All rights reserved. -// -// Licensed 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 com.google.devtools.build.lib.vfs; - -import com.google.common.annotations.VisibleForTesting; -import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadHostile; -import com.google.devtools.build.lib.util.Preconditions; - -import java.io.IOException; - -/** - * A file system that's capable of identifying paths residing outside its scope - * and using a delegator (such as {@link UnionFileSystem}) to re-route them - * to appropriate alternative file systems. - * - * <p>This is most useful for symlinks, which may ostensibly fall beneath some - * file system but resolve to paths outside that file system. - * - * <p>Note that we don't protect against cross-filesystem circular references. - * Therefore, care should be taken not to mix two scopable file systems that - * can reference each other. This theoretical safety cost is balanced by - * decreased code complexity requirements in implementations. - */ -public abstract class ScopeEscapableFileSystem extends FileSystem { - - private FileSystem delegator; - protected final PathFragment scopeRoot; - private boolean enableScopeChecking = true; // Used for testing. - - /** - * Instantiates a new ScopeEscapableFileSystem. - * - * @param scopeRoot the root path for the file system's scope. Any path - * that isn't beneath this one is considered out of scope according - * to {@link #inScope}. If null, scope checking is disabled. Note - * this is not the same thing as {@link FileSystem#rootPath}, which - * generally resolves to "/". - */ - protected ScopeEscapableFileSystem(PathFragment scopeRoot) { - this.scopeRoot = scopeRoot; - } - - @VisibleForTesting - void enableScopeChecking(boolean enable) { - this.enableScopeChecking = enable; - } - - /** - * Sets the delegator used to resolve paths that fall outside this file - * system's scope. - * - * <p>This method is not thread safe. It's intended to be called during - * instance initialization, not during active usage. The only reason this - * isn't set as immutable state within the constructor is that the delegator - * may need a reference to this instance for its own constructor. - */ - @ThreadHostile - public void setDelegator(FileSystem delegator) { - this.delegator = delegator; - } - - /** - * Uses the delegator to convert a path fragment to a path that's bound - * to the file system that manages that path. - */ - protected Path getDelegatedPath(PathFragment path) { - Preconditions.checkState(delegator != null); - return delegator.getPath(path); - } - - /** - * Proxy for {@link FileSystem#resolveOneLink} that sends the input path - * through the delegator. - */ - protected PathFragment resolveOneLinkWithDelegator(final PathFragment path) throws IOException { - Preconditions.checkState(delegator != null); - return delegator.resolveOneLink(getDelegatedPath(path)); - } - - /** - * Proxy for {@link FileSystem#stat} that sends the input path through - * the delegator. - */ - protected FileStatus statWithDelegator(final PathFragment path, final boolean followSymlinks) - throws IOException { - Preconditions.checkState(delegator != null); - return delegator.stat(getDelegatedPath(path), followSymlinks); - } - - /** - * Returns true if the given path is within this file system's scope, false - * otherwise. - * - * @param parentDepth the number of segments in the path's parent directory - * (only meaningful for paths that begin with ".."). The parent directory - * itself is assumed to be in scope. - * @param normalizedPath input path, expected to be normalized such that all - * ".." and "." segments are removed (with the exception of a possible - * prefix sequence of contiguous ".." segments) - */ - protected boolean inScope(int parentDepth, PathFragment normalizedPath) { - if (scopeRoot == null || !enableScopeChecking) { - return true; - } else if (normalizedPath.isAbsolute()) { - return normalizedPath.startsWith(scopeRoot); - } else { - // Efficiency note: we're not accounting for "/scope/root/../root" paths here, i.e. paths - // that appear to go out of scope but ultimately stay within scope. This may result in - // unnecessary re-delegation back into the same FS. we're choosing to forgo that - // optimization under the assumption that such scenarios are rare and unimportant to - // overall performance. We can always enhance this if needed. - return parentDepth - leadingParentReferences(normalizedPath) >= scopeRoot.segmentCount(); - } - } - - /** - * Given a path that's normalized (no ".." or "." segments), except for a possible - * prefix sequence of contiguous ".." segments, returns the size of that prefix - * sequence. - * - * <p>Example allowed inputs: "/absolute/path", "relative/path", "../../relative/path". - * Example disallowed inputs: "/absolute/path/../path2", "relative/../path", "../relative/../p". - */ - protected int leadingParentReferences(PathFragment normalizedPath) { - int leadingParentReferences = 0; - for (int i = 0; i < normalizedPath.segmentCount() && - normalizedPath.getSegment(i).equals(".."); i++) { - leadingParentReferences++; - } - return leadingParentReferences; - } -} diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java index 16c0f89380..b9a06ac213 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java @@ -16,21 +16,20 @@ package com.google.devtools.build.lib.vfs.inmemoryfs; import com.google.devtools.build.lib.clock.Clock; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.util.Preconditions; +import com.google.devtools.build.lib.vfs.FileStatus; import com.google.devtools.build.lib.vfs.Path; -import com.google.devtools.build.lib.vfs.PathFragment; import java.io.IOException; /** - * This interface defines the function directly supported by the "files" stored - * in a InMemoryFileSystem. This corresponds to a file or inode in UNIX: it - * doesn't have a path (it could have many paths due to hard links, or none if - * it's unlinked, i.e. garbage). + * This interface defines the function directly supported by the "files" stored in a + * InMemoryFileSystem. This corresponds to a file or inode in UNIX: it doesn't have a path (it could + * have many paths due to hard links, or none if it's unlinked, i.e. garbage). * - * <p>This class is thread-safe: instances may be accessed and modified from - * concurrent threads. Subclasses must preserve this property. + * <p>This class is thread-safe: instances may be accessed and modified from concurrent threads. + * Subclasses must preserve this property. */ @ThreadSafe -public abstract class InMemoryContentInfo implements ScopeEscapableStatus { +public abstract class InMemoryContentInfo implements FileStatus { private final Clock clock; @@ -196,16 +195,6 @@ public abstract class InMemoryContentInfo implements ScopeEscapableStatus { return isExecutable; } - @Override - public boolean outOfScope() { - return false; - } - - @Override - public PathFragment getEscapingPath() { - return null; - } - /** * Called just before this inode is moved. * diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java index b3e59d18b0..d2db9967a1 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java @@ -20,10 +20,9 @@ import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.util.Preconditions; import com.google.devtools.build.lib.vfs.FileAccessException; import com.google.devtools.build.lib.vfs.FileStatus; +import com.google.devtools.build.lib.vfs.FileSystem; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; -import com.google.devtools.build.lib.vfs.ScopeEscapableFileSystem; -import com.google.devtools.build.lib.vfs.Symlinks; import java.io.ByteArrayInputStream; import java.io.FileNotFoundException; import java.io.IOException; @@ -38,26 +37,18 @@ import javax.annotation.Nullable; /** * This class provides a complete in-memory file system. * - * <p>Naming convention: we use "path" for all {@link Path} variables, since these - * represent *names* and we use "node" or "inode" for InMemoryContentInfo - * variables, since these correspond to inodes in the UNIX file system. + * <p>Naming convention: we use "path" for all {@link Path} variables, since these represent *names* + * and we use "node" or "inode" for InMemoryContentInfo variables, since these correspond to inodes + * in the UNIX file system. * - * <p>The code is structured to be as similar to the implementation of UNIX "namei" - * as is reasonably possibly. This provides a firm reference point for many - * concepts and makes compatibility easier to achieve. - * - * <p>As a scope-escapable file system, this class supports re-delegation of symbolic links - * that escape its root. This is done through the use of {@link OutOfScopeFileStatus} - * and {@link OutOfScopeDirectoryStatus} objects, which may be returned by - * getDirectory, pathWalk, and scopeLimitedStat. Any code that calls one of these - * methods (either directly or indirectly) is obligated to check the possibility - * that its info represents an out-of-scope path. Lack of such a check will result - * in unchecked runtime exceptions upon any request for status data (as well as - * possible logical errors). + * <p>The code is structured to be as similar to the implementation of UNIX "namei" as is reasonably + * possibly. This provides a firm reference point for many concepts and makes compatibility easier + * to achieve. */ @ThreadSafe -public class InMemoryFileSystem extends ScopeEscapableFileSystem { +public class InMemoryFileSystem extends FileSystem { + private final PathFragment scopeRoot; private final Clock clock; // The root inode (a directory). @@ -83,12 +74,11 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { } /** - * Creates a new InMemoryFileSystem with scope checking bound to - * scopeRoot, i.e. any path that's not below scopeRoot is considered - * to be out of scope. + * Creates a new InMemoryFileSystem with scope checking bound to scopeRoot, i.e. any path that's + * not below scopeRoot is considered to be out of scope. */ - protected InMemoryFileSystem(Clock clock, PathFragment scopeRoot) { - super(scopeRoot); + public InMemoryFileSystem(Clock clock, PathFragment scopeRoot) { + this.scopeRoot = scopeRoot; this.clock = clock; this.rootInode = new InMemoryDirectoryInfo(clock); rootInode.addChild(".", rootInode); @@ -96,6 +86,46 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { } /** + * Returns true if the given path is within this file system's scope, false otherwise. + * + * @param parentDepth the number of segments in the path's parent directory (only meaningful for + * paths that begin with ".."). The parent directory itself is assumed to be in scope. + * @param normalizedPath input path, expected to be normalized such that all ".." and "." segments + * are removed (with the exception of a possible prefix sequence of contiguous ".." segments) + */ + private boolean inScope(int parentDepth, PathFragment normalizedPath) { + if (scopeRoot == null) { + return true; + } else if (normalizedPath.isAbsolute()) { + return normalizedPath.startsWith(scopeRoot); + } else { + // Efficiency note: we're not accounting for "/scope/root/../root" paths here, i.e. paths + // that appear to go out of scope but ultimately stay within scope. This may result in + // unnecessary re-delegation back into the same FS. we're choosing to forgo that + // optimization under the assumption that such scenarios are rare and unimportant to + // overall performance. We can always enhance this if needed. + return parentDepth - leadingParentReferences(normalizedPath) >= scopeRoot.segmentCount(); + } + } + + /** + * Given a path that's normalized (no ".." or "." segments), except for a possible prefix sequence + * of contiguous ".." segments, returns the size of that prefix sequence. + * + * <p>Example allowed inputs: "/absolute/path", "relative/path", "../../relative/path". Example + * disallowed inputs: "/absolute/path/../path2", "relative/../path", "../relative/../p". + */ + private int leadingParentReferences(PathFragment normalizedPath) { + int leadingParentReferences = 0; + for (int i = 0; + i < normalizedPath.segmentCount() && normalizedPath.getSegment(i).equals(".."); + i++) { + leadingParentReferences++; + } + return leadingParentReferences; + } + + /** * The errors that {@link InMemoryFileSystem} might issue for different sorts of IO failures. */ public enum Error { @@ -110,7 +140,7 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { private final String message; - private Error(String message) { + Error(String message) { this.message = message; } @@ -120,7 +150,7 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { } /** Implemented by exceptions that contain the extra info of which Error caused them. */ - private static interface WithError { + private interface WithError { Error getError(); } @@ -271,7 +301,7 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { if (!create) { throw Error.ENOENT.exception(path); } else { - child = makeFileInfo(clock, path.asFragment()); + child = new InMemoryFileInfo(clock); insert(imdi, name, child, path); } } @@ -279,21 +309,18 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { } /** - * Low-level path-to-inode lookup routine. Analogous to path_walk() in many - * UNIX kernels. Given 'path', walks the directory tree from the root, - * resolving all symbolic links, and returns the designated inode. + * Low-level path-to-inode lookup routine. Analogous to path_walk() in many UNIX kernels. Given + * 'path', walks the directory tree from the root, resolving all symbolic links, and returns the + * designated inode. * - * <p>If 'create' is false, the inode must exist; otherwise, it will be created - * and added to its parent directory, which must exist. + * <p>If 'create' is false, the inode must exist; otherwise, it will be created and added to its + * parent directory, which must exist. * - * <p>Iff the given path escapes this file system's scope, the returned value - * is an {@link OutOfScopeFileStatus} instance. Any code that calls this method - * needs to check for that possibility (via {@link ScopeEscapableStatus#outOfScope}). + * <p>Iff the given path escapes this file system's scope, a Error.ENOENT exception is thrown. * * <p>May fail with ENOTDIR, ENOENT, EACCES, ELOOP. */ - private synchronized InMemoryContentInfo pathWalk(Path path, boolean create) - throws IOException { + private synchronized InMemoryContentInfo pathWalk(Path path, boolean create) throws IOException { // Implementation note: This is where we check for out-of-scope symlinks and // trigger re-delegation to another file system accordingly. This code handles // both absolute and relative symlinks. Some assumptions we make: First, only @@ -325,7 +352,7 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { if (child.isSymbolicLink()) { PathFragment linkTarget = ((InMemoryLinkInfo) child).getNormalizedLinkContent(); if (!inScope(parentDepth, linkTarget)) { - return outOfScopeStatus(linkTarget, parentDepth, stack); + throw Error.ENOENT.exception(path); } if (linkTarget.isAbsolute()) { inode = rootInode; @@ -345,62 +372,14 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { } /** - * Helper routine for pathWalk: given a symlink target known to escape this file system's - * scope (and that has the form [".."]*[standard segment]+), the number of segments - * in the directory containing the symlink, and the remaining path segments following - * the symlink in the original input to pathWalk, returns an OutofScopeFileStatus - * initialized with an appropriate out-of-scope reformulation of pathWalk's original - * input. - */ - private OutOfScopeFileStatus outOfScopeStatus(PathFragment linkTarget, int parentDepth, - Stack<String> descendantSegments) { - - PathFragment escapingPath; - if (linkTarget.isAbsolute()) { - escapingPath = linkTarget; - } else { - // Relative out-of-scope paths must look like "../../../a/b/c". Find the target's - // parent path depth by subtracting one from parentDepth for each ".." reference. - // Then use that to retrieve a prefix of the scope root, which is the target's - // canonicalized parent path. - int leadingParentRefs = leadingParentReferences(linkTarget); - int baseDepth = parentDepth - leadingParentRefs; - Preconditions.checkState(baseDepth < scopeRoot.segmentCount()); - escapingPath = baseDepth > 0 - ? scopeRoot.subFragment(0, baseDepth) - : scopeRoot.subFragment(0, 0); - // Now add in everything that comes after the ".." sequence. - for (int i = leadingParentRefs; i < linkTarget.segmentCount(); i++) { - escapingPath = escapingPath.getRelative(linkTarget.getSegment(i)); - } - } - - // We've now converted the symlink to its target in canonicalized absolute path - // form. Since the symlink wasn't necessarily the final segment in the original - // input sent to pathWalk, now add in every segment that came after. - while (!descendantSegments.empty()) { - escapingPath = escapingPath.getRelative(descendantSegments.pop()); - } - - return new OutOfScopeFileStatus(escapingPath); - } - - /** * Given 'path', returns the existing directory inode it designates, * following symbolic links. * * <p>May fail with ENOTDIR, or any exception from pathWalk. - * - * <p>Iff the given path escapes this file system's scope, this method skips - * ENOTDIR checking and returns an OutOfScopeDirectoryStatus instance. Any - * code that calls this method needs to check for that possibility - * (via {@link ScopeEscapableStatus#outOfScope}). */ private InMemoryDirectoryInfo getDirectory(Path path) throws IOException { InMemoryContentInfo dirInfo = pathWalk(path, false); - if (dirInfo.outOfScope()) { - return new OutOfScopeDirectoryStatus(dirInfo.getEscapingPath()); - } else if (!dirInfo.isDirectory()) { + if (!dirInfo.isDirectory()) { throw Error.ENOTDIR.exception(path); } else { return (InMemoryDirectoryInfo) dirInfo; @@ -416,9 +395,7 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { private synchronized InMemoryContentInfo getNoFollowStatOrOutOfScopeParent(Path path) throws IOException { InMemoryDirectoryInfo dirInfo = getDirectory(path.getParentDirectory()); - return dirInfo.outOfScope() - ? dirInfo - : directoryLookup(dirInfo, path.getBaseName(), /*create=*/false, path); + return directoryLookup(dirInfo, path.getBaseName(), /*create=*/ false, path); } /** @@ -429,21 +406,12 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { @Override public FileStatus stat(Path path, boolean followSymlinks) throws IOException { if (followSymlinks) { - InMemoryContentInfo status = scopeLimitedStat(path, true); - return status.outOfScope() - ? statWithDelegator(status.getEscapingPath(), true) - : status; + return scopeLimitedStat(path, true); } else { if (path.equals(rootPath)) { return rootInode; } else { - InMemoryContentInfo status = getNoFollowStatOrOutOfScopeParent(path); - // If out of scope, status references the path's parent directory. Else it references the - // path itself. - return status.outOfScope() - ? getDelegatedPath(status.getEscapingPath().getRelative( - path.getBaseName())).stat(Symlinks.NOFOLLOW) - : status; + return getNoFollowStatOrOutOfScopeParent(path); } } } @@ -465,13 +433,10 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { } /** - * Version of stat that returns an inode if the input path stays entirely within - * this file system's scope, otherwise an {@link OutOfScopeFileStatus}. - * - * <p>Any code that calls this method needs to check for either possibility via - * {@link ScopeEscapableStatus#outOfScope}. + * Version of stat that returns an inode if the input path stays entirely within this file + * system's scope, otherwise throws. */ - protected InMemoryContentInfo scopeLimitedStat(Path path, boolean followSymlinks) + private InMemoryContentInfo scopeLimitedStat(Path path, boolean followSymlinks) throws IOException { if (followSymlinks) { return pathWalk(path, false); @@ -479,12 +444,7 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { if (path.equals(rootPath)) { return rootInode; } else { - InMemoryContentInfo status = getNoFollowStatOrOutOfScopeParent(path); - // If out of scope, status references the path's parent directory. Else it references the - // path itself. - return status.outOfScope() - ? new OutOfScopeFileStatus(status.getEscapingPath().getRelative(path.getBaseName())) - : status; + return getNoFollowStatOrOutOfScopeParent(path); } } } @@ -507,13 +467,7 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { // Beware, this seemingly simple code belies the complex specification of // FileSystem.resolveOneLink(). InMemoryContentInfo status = scopeLimitedStat(path, false); - if (status.outOfScope()) { - return resolveOneLinkWithDelegator(status.getEscapingPath()); - } else { - return status.isSymbolicLink() - ? ((InMemoryLinkInfo) status).getLinkContent() - : null; - } + return status.isSymbolicLink() ? ((InMemoryLinkInfo) status).getLinkContent() : null; } @Override @@ -562,47 +516,24 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { } } - /** - * Like {@link #exists}, but checks for existence within this filesystem's scope. - */ - protected boolean scopeLimitedExists(Path path, boolean followSymlinks) { - try { - // Path#asFragment() always returns an absolute path, so inScope() is called with - // parentDepth = 0. - return inScope(0, path.asFragment()) && !scopeLimitedStat(path, followSymlinks).outOfScope(); - } catch (IOException e) { - return false; - } - } - @Override protected boolean isReadable(Path path) throws IOException { InMemoryContentInfo status = scopeLimitedStat(path, true); - return status.outOfScope() - ? getDelegatedPath(status.getEscapingPath()).isReadable() - : status.isReadable(); + return status.isReadable(); } @Override protected void setReadable(Path path, boolean readable) throws IOException { - InMemoryContentInfo status; synchronized (this) { - status = scopeLimitedStat(path, true); - if (!status.outOfScope()) { - status.setReadable(readable); - return; - } + InMemoryContentInfo status = scopeLimitedStat(path, true); + status.setReadable(readable); } - // If we get here, we're out of scope. - getDelegatedPath(status.getEscapingPath()).setReadable(readable); } @Override protected boolean isWritable(Path path) throws IOException { InMemoryContentInfo status = scopeLimitedStat(path, true); - return status.outOfScope() - ? getDelegatedPath(status.getEscapingPath()).isWritable() - : status.isWritable(); + return status.isWritable(); } @Override @@ -610,36 +541,23 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { InMemoryContentInfo status; synchronized (this) { status = scopeLimitedStat(path, true); - if (!status.outOfScope()) { - status.setWritable(writable); - return; - } + status.setWritable(writable); } - // If we get here, we're out of scope. - getDelegatedPath(status.getEscapingPath()).setWritable(writable); } @Override protected boolean isExecutable(Path path) throws IOException { InMemoryContentInfo status = scopeLimitedStat(path, true); - return status.outOfScope() - ? getDelegatedPath(status.getEscapingPath()).isExecutable() - : status.isExecutable(); + return status.isExecutable(); } @Override protected void setExecutable(Path path, boolean executable) throws IOException { - InMemoryContentInfo status; synchronized (this) { - status = scopeLimitedStat(path, true); - if (!status.outOfScope()) { - status.setExecutable(executable); - return; - } + InMemoryContentInfo status = scopeLimitedStat(path, true); + status.setExecutable(executable); } - // If we get here, we're out of scope. - getDelegatedPath(status.getEscapingPath()).setExecutable(executable); } @Override @@ -662,25 +580,6 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { return OS.getCurrent() != OS.WINDOWS; } - /** - * Constructs a new inode. Provided so that subclasses of InMemoryFileSystem - * can inject subclasses of FileInfo properly. - */ - protected FileInfo makeFileInfo(Clock clock, PathFragment frag) { - return new InMemoryFileInfo(clock); - } - - /** - * Returns a new path constructed by appending the child's base name to the - * escaped parent path. For example, assume our file system root is /foo - * and /foo/link1 -> /bar. This method can be used on child = /foo/link1/link2/name - * and parent = /bar/link2 to return /bar/link2/name, which is a semi-resolved - * path bound to a different file system. - */ - private Path getDelegatedPath(PathFragment escapedParent, Path child) { - return getDelegatedPath(escapedParent.getRelative(child.getBaseName())); - } - @Override protected boolean createDirectory(Path path) throws IOException { if (path.equals(rootPath)) { throw Error.EACCES.exception(path); } @@ -688,27 +587,22 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { InMemoryDirectoryInfo parent; synchronized (this) { parent = getDirectory(path.getParentDirectory()); - if (!parent.outOfScope()) { - InMemoryContentInfo child = parent.getChild(path.getBaseName()); - if (child != null) { // already exists - if (child.isDirectory()) { - return false; - } else { - throw Error.EEXIST.exception(path); - } + InMemoryContentInfo child = parent.getChild(path.getBaseName()); + if (child != null) { // already exists + if (child.isDirectory()) { + return false; + } else { + throw Error.EEXIST.exception(path); } + } - InMemoryDirectoryInfo newDir = new InMemoryDirectoryInfo(clock); - newDir.addChild(".", newDir); - newDir.addChild("..", parent); - insert(parent, path.getBaseName(), newDir, path); + InMemoryDirectoryInfo newDir = new InMemoryDirectoryInfo(clock); + newDir.addChild(".", newDir); + newDir.addChild("..", parent); + insert(parent, path.getBaseName(), newDir, path); - return true; - } + return true; } - - // If we get here, we're out of scope. - return getDelegatedPath(parent.getEscapingPath(), path).createDirectory(); } @Override @@ -716,26 +610,19 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { throws IOException { if (path.equals(rootPath)) { throw Error.EACCES.exception(path); } - InMemoryDirectoryInfo parent; synchronized (this) { - parent = getDirectory(path.getParentDirectory()); - if (!parent.outOfScope()) { - if (parent.getChild(path.getBaseName()) != null) { throw Error.EEXIST.exception(path); } - insert(parent, path.getBaseName(), new InMemoryLinkInfo(clock, targetFragment), path); - return; + InMemoryDirectoryInfo parent = getDirectory(path.getParentDirectory()); + if (parent.getChild(path.getBaseName()) != null) { + throw Error.EEXIST.exception(path); } + insert(parent, path.getBaseName(), new InMemoryLinkInfo(clock, targetFragment), path); } - - // If we get here, we're out of scope. - getDelegatedPath(parent.getEscapingPath(), path).createSymbolicLink(targetFragment); } @Override protected PathFragment readSymbolicLink(Path path) throws IOException { InMemoryContentInfo status = scopeLimitedStat(path, false); - if (status.outOfScope()) { - return getDelegatedPath(status.getEscapingPath()).readSymbolicLink(); - } else if (status.isSymbolicLink()) { + if (status.isSymbolicLink()) { Preconditions.checkState(status instanceof InMemoryLinkInfo); return ((InMemoryLinkInfo) status).getLinkContent(); } else { @@ -751,29 +638,23 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { @Override protected Collection<Path> getDirectoryEntries(Path path) throws IOException { - InMemoryDirectoryInfo dirInfo; synchronized (this) { - dirInfo = getDirectory(path); - if (!dirInfo.outOfScope()) { - FileStatus status = stat(path, false); - Preconditions.checkState(status instanceof InMemoryContentInfo); - if (!((InMemoryContentInfo) status).isReadable()) { - throw new IOException("Directory is not readable"); - } + InMemoryDirectoryInfo dirInfo = getDirectory(path); + FileStatus status = stat(path, false); + Preconditions.checkState(status instanceof InMemoryContentInfo); + if (!((InMemoryContentInfo) status).isReadable()) { + throw new IOException("Directory is not readable"); + } - Collection<String> allChildren = dirInfo.getAllChildren(); - List<Path> result = new ArrayList<>(allChildren.size()); - for (String child : allChildren) { - if (!(child.equals(".") || child.equals(".."))) { - result.add(path.getChild(child)); - } + Collection<String> allChildren = dirInfo.getAllChildren(); + List<Path> result = new ArrayList<>(allChildren.size()); + for (String child : allChildren) { + if (!(child.equals(".") || child.equals(".."))) { + result.add(path.getChild(child)); } - return result; } + return result; } - - // If we get here, we're out of scope. - return getDelegatedPath(dirInfo.getEscapingPath()).getDirectoryEntries(); } @Override @@ -781,19 +662,15 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { if (path.equals(rootPath)) { throw Error.EBUSY.exception(path); } if (!exists(path, false)) { return false; } - InMemoryDirectoryInfo parent; synchronized (this) { - parent = getDirectory(path.getParentDirectory()); - if (!parent.outOfScope()) { - InMemoryContentInfo child = parent.getChild(path.getBaseName()); - if (child.isDirectory() && child.getSize() > 2) { throw Error.ENOTEMPTY.exception(path); } - unlink(parent, path.getBaseName(), path); - return true; + InMemoryDirectoryInfo parent = getDirectory(path.getParentDirectory()); + InMemoryContentInfo child = parent.getChild(path.getBaseName()); + if (child.isDirectory() && child.getSize() > 2) { + throw Error.ENOTEMPTY.exception(path); } + unlink(parent, path.getBaseName(), path); + return true; } - - // If we get here, we're out of scope. - return getDelegatedPath(parent.getEscapingPath(), path).delete(); } @Override @@ -804,46 +681,29 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { @Override protected void setLastModifiedTime(Path path, long newTime) throws IOException { - InMemoryContentInfo status; synchronized (this) { - status = scopeLimitedStat(path, true); - if (!status.outOfScope()) { - status.setLastModifiedTime(newTime == -1L - ? clock.currentTimeMillis() - : newTime); - return; - } + InMemoryContentInfo status = scopeLimitedStat(path, true); + status.setLastModifiedTime(newTime == -1L ? clock.currentTimeMillis() : newTime); } - - // If we get here, we're out of scope. - getDelegatedPath(status.getEscapingPath()).setLastModifiedTime(newTime); } @Override protected InputStream getInputStream(Path path) throws IOException { - InMemoryContentInfo status; synchronized (this) { - status = scopeLimitedStat(path, true); - if (!status.outOfScope()) { - if (status.isDirectory()) { throw Error.EISDIR.exception(path); } - if (!path.isReadable()) { throw Error.EACCES.exception(path); } - Preconditions.checkState(status instanceof FileInfo); - return new ByteArrayInputStream(((FileInfo) status).readContent()); + InMemoryContentInfo status = scopeLimitedStat(path, true); + if (status.isDirectory()) { + throw Error.EISDIR.exception(path); + } + if (!path.isReadable()) { + throw Error.EACCES.exception(path); } + Preconditions.checkState(status instanceof FileInfo); + return new ByteArrayInputStream(((FileInfo) status).readContent()); } - - // If we get here, we're out of scope. - return getDelegatedPath(status.getEscapingPath()).getInputStream(); } - /** - * Creates a new file at the given path and returns its inode. If the path - * escapes this file system's scope, trivially returns an "out of scope" status. - * Calling code should check for both possibilities via - * {@link ScopeEscapableStatus#outOfScope}. - */ - protected InMemoryContentInfo getOrCreateWritableInode(Path path) - throws IOException { + /** Creates a new file at the given path and returns its inode. */ + private InMemoryContentInfo getOrCreateWritableInode(Path path) throws IOException { // open(WR_ONLY) of a dangling link writes through the link. That means // that the usual path lookup operations have to behave differently when // resolving a path with the intent to create it: instead of failing with @@ -851,9 +711,7 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { // kernels do it, which is what we're trying to emulate. InMemoryContentInfo child = pathWalk(path, /*create=*/true); Preconditions.checkNotNull(child); - if (child.outOfScope()) { - return child; - } else if (child.isDirectory()) { + if (child.isDirectory()) { throw Error.EISDIR.exception(path); } else { // existing or newly-created file if (!child.isWritable()) { throw Error.EACCES.exception(path); } @@ -864,74 +722,58 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { @Override protected OutputStream getOutputStream(Path path, boolean append) throws IOException { - InMemoryContentInfo status; synchronized (this) { - status = getOrCreateWritableInode(path); - if (!status.outOfScope()) { - return ((FileInfo) getOrCreateWritableInode(path)).getOutputStream(append); - } + InMemoryContentInfo status = getOrCreateWritableInode(path); + return ((FileInfo) status).getOutputStream(append); } - // If we get here, we're out of scope. - return getDelegatedPath(status.getEscapingPath()).getOutputStream(append); } @Override protected void renameTo(Path sourcePath, Path targetPath) throws IOException { - if (sourcePath.equals(rootPath)) { throw Error.EACCES.exception(sourcePath); } - if (targetPath.equals(rootPath)) { throw Error.EACCES.exception(targetPath); } + if (sourcePath.equals(rootPath)) { + throw Error.EACCES.exception(sourcePath); + } + if (targetPath.equals(rootPath)) { + throw Error.EACCES.exception(targetPath); + } + synchronized (this) { + InMemoryDirectoryInfo sourceParent = getDirectory(sourcePath.getParentDirectory()); + InMemoryDirectoryInfo targetParent = getDirectory(targetPath.getParentDirectory()); + + InMemoryContentInfo sourceInode = sourceParent.getChild(sourcePath.getBaseName()); + if (sourceInode == null) { + throw Error.ENOENT.exception(sourcePath); + } + InMemoryContentInfo targetInode = targetParent.getChild(targetPath.getBaseName()); - InMemoryDirectoryInfo sourceParent; - InMemoryDirectoryInfo targetParent; + unlink(sourceParent, sourcePath.getBaseName(), sourcePath); + try { + // TODO(bazel-team): (2009) test with symbolic links. - synchronized (this) { - sourceParent = getDirectory(sourcePath.getParentDirectory()); - targetParent = getDirectory(targetPath.getParentDirectory()); - - // Handle the rename if both paths are within our scope. - if (!sourceParent.outOfScope() && !targetParent.outOfScope()) { - InMemoryContentInfo sourceInode = sourceParent.getChild(sourcePath.getBaseName()); - if (sourceInode == null) { throw Error.ENOENT.exception(sourcePath); } - InMemoryContentInfo targetInode = targetParent.getChild(targetPath.getBaseName()); - - unlink(sourceParent, sourcePath.getBaseName(), sourcePath); - try { - // TODO(bazel-team): (2009) test with symbolic links. - - // Precondition checks: - if (targetInode != null) { // already exists - if (targetInode.isDirectory()) { - if (!sourceInode.isDirectory()) { - throw new IOException(sourcePath + " -> " + targetPath + " (" + Error.EISDIR + ")"); - } - if (targetInode.getSize() > 2) { - throw Error.ENOTEMPTY.exception(targetPath); - } - } else if (sourceInode.isDirectory()) { - throw new IOException(sourcePath + " -> " + targetPath + " (" + Error.ENOTDIR + ")"); + // Precondition checks: + if (targetInode != null) { // already exists + if (targetInode.isDirectory()) { + if (!sourceInode.isDirectory()) { + throw new IOException(sourcePath + " -> " + targetPath + " (" + Error.EISDIR + ")"); } - unlink(targetParent, targetPath.getBaseName(), targetPath); + if (targetInode.getSize() > 2) { + throw Error.ENOTEMPTY.exception(targetPath); + } + } else if (sourceInode.isDirectory()) { + throw new IOException(sourcePath + " -> " + targetPath + " (" + Error.ENOTDIR + ")"); } - sourceInode.movedTo(targetPath); - insert(targetParent, targetPath.getBaseName(), sourceInode, targetPath); - return; - - } catch (IOException e) { - sourceInode.movedTo(sourcePath); - insert(sourceParent, sourcePath.getBaseName(), sourceInode, sourcePath); // restore source - throw e; + unlink(targetParent, targetPath.getBaseName(), targetPath); } - } - } + sourceInode.movedTo(targetPath); + insert(targetParent, targetPath.getBaseName(), sourceInode, targetPath); + return; - // If we get here, either one or both paths is out of scope. - if (sourceParent.outOfScope() && targetParent.outOfScope()) { - Path delegatedSource = getDelegatedPath(sourceParent.getEscapingPath(), sourcePath); - Path delegatedTarget = getDelegatedPath(targetParent.getEscapingPath(), targetPath); - delegatedSource.renameTo(delegatedTarget); - } else { - // We don't support cross-file system renaming. - throw Error.EACCES.exception(targetPath); + } catch (IOException e) { + sourceInode.movedTo(sourcePath); + insert(sourceParent, sourcePath.getBaseName(), sourceInode, sourcePath); // restore source + throw e; + } } } @@ -944,23 +786,17 @@ public class InMemoryFileSystem extends ScopeEscapableFileSystem { throw Error.EACCES.exception(originalPath); } - InMemoryDirectoryInfo linkParent; synchronized (this) { - linkParent = getDirectory(linkPath.getParentDirectory()); + InMemoryDirectoryInfo linkParent = getDirectory(linkPath.getParentDirectory()); // Same check used when creating a symbolic link - if (!linkParent.outOfScope()) { - if (linkParent.getChild(linkPath.getBaseName()) != null) { - throw Error.EEXIST.exception(linkPath); - } - insert( - linkParent, - linkPath.getBaseName(), - getDirectory(originalPath.getParentDirectory()).getChild(originalPath.getBaseName()), - linkPath); - return; + if (linkParent.getChild(linkPath.getBaseName()) != null) { + throw Error.EEXIST.exception(linkPath); } + insert( + linkParent, + linkPath.getBaseName(), + getDirectory(originalPath.getParentDirectory()).getChild(originalPath.getBaseName()), + linkPath); } - // If we get here, we're out of scope. - getDelegatedPath(linkParent.getEscapingPath(), originalPath).createHardLink(linkPath); } } diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeDirectoryStatus.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeDirectoryStatus.java deleted file mode 100644 index 95d6044144..0000000000 --- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeDirectoryStatus.java +++ /dev/null @@ -1,123 +0,0 @@ -// Copyright 2014 The Bazel Authors. All rights reserved. -// -// Licensed 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 com.google.devtools.build.lib.vfs.inmemoryfs; - -import com.google.devtools.build.lib.vfs.PathFragment; - -import java.util.Set; - -/** - * A directory status that signifies a path has left this file system's - * scope. All methods beside {@link #outOfScope} and {@link #getEscapingPath} - * are disabled. - */ -final class OutOfScopeDirectoryStatus extends InMemoryDirectoryInfo { - /** - * Contains the requested path resolved up to the point where it - * first escapes the scope. See - * {@link ScopeEscapableStatus#getEscapingPath} for an example. - */ - private final PathFragment escapingPath; - - public OutOfScopeDirectoryStatus(PathFragment escapingPath) { - super(null, false); - this.escapingPath = escapingPath; - } - - @Override - public boolean outOfScope() { - return true; - } - - @Override - public PathFragment getEscapingPath() { - return escapingPath; - } - - private static UnsupportedOperationException failure() { - return new UnsupportedOperationException(); - } - - @Override public boolean isDirectory() { - throw failure(); - } - - @Override public boolean isSymbolicLink() { - throw failure(); - } - - @Override public boolean isFile() { - throw failure(); - } - - @Override public long getSize() { - throw failure(); - } - - @Override protected synchronized void markModificationTime() { - throw failure(); - } - - @Override public synchronized long getLastModifiedTime() { - throw failure(); - } - - @Override synchronized void setLastModifiedTime(long newTime) { - throw failure(); - } - - @Override public synchronized long getLastChangeTime() { - throw failure(); - } - - @Override boolean isReadable() { - throw failure(); - } - - @Override void setReadable(boolean readable) { - throw failure(); - } - - @Override void setWritable(boolean writable) { - throw failure(); - } - - @Override void setExecutable(boolean executable) { - throw failure(); - } - - @Override boolean isWritable() { - throw failure(); - } - - @Override boolean isExecutable() { - throw failure(); - } - - @Override synchronized void addChild(String name, InMemoryContentInfo inode) { - throw failure(); - } - - @Override synchronized InMemoryContentInfo getChild(String name) { - throw failure(); - } - - @Override synchronized void removeChild(String name) { - throw failure(); - } - - @Override Set<String> getAllChildren() { - throw failure(); - } -} diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeFileStatus.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeFileStatus.java deleted file mode 100644 index 0804968091..0000000000 --- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeFileStatus.java +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2014 The Bazel Authors. All rights reserved. -// -// Licensed 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 com.google.devtools.build.lib.vfs.inmemoryfs; - -import com.google.devtools.build.lib.vfs.PathFragment; - -/** - * A file status that signifies a path has left this file system's - * scope. All methods beside {@link #outOfScope} and {@link #getEscapingPath} - * are disabled. - */ -final class OutOfScopeFileStatus extends InMemoryContentInfo { - - /** - * Contains the requested path resolved up to the point where it - * first escapes the scope. See - * {@link ScopeEscapableStatus#getEscapingPath} for an example. - */ - private final PathFragment escapingPath; - - public OutOfScopeFileStatus(PathFragment escapingPath) { - super(null, false); - this.escapingPath = escapingPath; - } - - @Override - public boolean outOfScope() { - return true; - } - - @Override - public PathFragment getEscapingPath() { - return escapingPath; - } - - private static UnsupportedOperationException failure() { - return new UnsupportedOperationException(); - } - - @Override public boolean isDirectory() { - throw failure(); - } - - @Override public boolean isSymbolicLink() { - throw failure(); - } - - @Override public boolean isFile() { - throw failure(); - } - - @Override public boolean isSpecialFile() { - throw failure(); - } - - @Override public long getSize() { - throw failure(); - } - - @Override protected synchronized void markModificationTime() { - throw failure(); - } - - @Override public synchronized long getLastModifiedTime() { - throw failure(); - } - - @Override synchronized void setLastModifiedTime(long newTime) { - throw failure(); - } - - @Override public synchronized long getLastChangeTime() { - throw failure(); - } - - @Override boolean isReadable() { - throw failure(); - } - - @Override void setReadable(boolean readable) { - throw failure(); - } - - @Override void setWritable(boolean writable) { - throw failure(); - } - - @Override boolean isWritable() { - throw failure(); - } - - @Override void setExecutable(boolean executable) { - throw failure(); - } - - @Override boolean isExecutable() { - throw failure(); - } -} diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/ScopeEscapableStatus.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/ScopeEscapableStatus.java deleted file mode 100644 index 371637985d..0000000000 --- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/ScopeEscapableStatus.java +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright 2014 The Bazel Authors. All rights reserved. -// -// Licensed 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 com.google.devtools.build.lib.vfs.inmemoryfs; - -import com.google.devtools.build.lib.vfs.FileStatus; -import com.google.devtools.build.lib.vfs.PathFragment; -import com.google.devtools.build.lib.vfs.ScopeEscapableFileSystem; - -/** - * Interface definition for a file status that may signify that the - * referenced path falls outside the scope of the file system (see - * {@link ScopeEscapableFileSystem}) and can provide the "escaped" - * version of that path suitable for re-delegation to another file - * system. - */ -interface ScopeEscapableStatus extends FileStatus { - - /** - * Returns true if this status corresponds to a path that leaves - * the file system's scope, false otherwise. - */ - boolean outOfScope(); - - /** - * If this status represents a path that leaves the file system's scope, - * returns the requested path resolved up to the point where it first - * escapes the file system. For example: if the file system is mapped to - * /foo, the requested path is /foo/link1/link2/link3, and link1 -> /bar, - * this returns /bar/link2/link3. - * - * <p>If this status doesn't represent a scope-escaping path, returns - * null. - */ - PathFragment getEscapingPath(); -} diff --git a/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java deleted file mode 100644 index 9fd98b0f6d..0000000000 --- a/src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java +++ /dev/null @@ -1,818 +0,0 @@ -// Copyright 2014 The Bazel Authors. All rights reserved. -// -// Licensed 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 com.google.devtools.build.lib.vfs; - -import static com.google.common.truth.Truth.assertThat; -import static java.nio.charset.StandardCharsets.UTF_8; -import static org.junit.Assert.fail; - -import com.google.common.collect.ImmutableList; -import com.google.devtools.build.lib.util.Preconditions; -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; -import java.util.Collection; -import org.junit.Before; -import org.junit.Test; - -/** - * Generic tests for any file system that implements {@link ScopeEscapableFileSystem}, - * i.e. any file system that supports symlinks that escape its scope. - * - * Each suitable file system test should inherit from this class, thereby obtaining - * all the tests. - */ -public abstract class ScopeEscapableFileSystemTest extends SymlinkAwareFileSystemTest { - - /** - * Trivial FileSystem implementation that can record the last path passed to each method - * and read/write to a unified "state" variable (which can then be checked by tests) for - * each data type this class manipulates. - * - * The default implementation of each method throws an exception. Each test case should - * selectively override the methods it expects to be invoked. - */ - private static class TestDelegator extends FileSystem { - protected Path lastPath; - protected boolean booleanState; - protected long longState; - protected Object objectState; - - public void setState(boolean state) { booleanState = state; } - public void setState(long state) { longState = state; } - public void setState(Object state) { objectState = state; } - - public boolean booleanState() { return booleanState; } - public long longState() { return longState; } - public Object objectState() { return objectState; } - - public PathFragment lastPath() { - Path ans = lastPath; - // Clear this out to protect against accidental matches when testing the same path multiple - // consecutive times. - lastPath = null; - return ans != null ? ans.asFragment() : null; - } - - @Override - public boolean supportsModifications(Path path) { - return true; - } - - @Override - public boolean supportsSymbolicLinksNatively(Path path) { - return true; - } - - @Override - public boolean supportsHardLinksNatively(Path path) { - return true; - } - - @Override - public boolean isFilePathCaseSensitive() { - return true; - } - - private static RuntimeException re() { - return new RuntimeException("This method should not be called in this context"); - } - - @Override protected boolean isReadable(Path path) { throw re(); } - @Override protected boolean isWritable(Path path) { throw re(); } - @Override protected boolean isDirectory(Path path, boolean followSymlinks) { throw re(); } - @Override protected boolean isFile(Path path, boolean followSymlinks) { throw re(); } - @Override protected boolean isSpecialFile(Path path, boolean followSymlinks) { throw re(); } - @Override protected boolean isExecutable(Path path) { throw re(); } - @Override protected boolean exists(Path path, boolean followSymlinks) {throw re(); } - @Override protected boolean isSymbolicLink(Path path) { throw re(); } - @Override protected boolean createDirectory(Path path) { throw re(); } - @Override protected boolean delete(Path path) { throw re(); } - - @Override protected long getFileSize(Path path, boolean followSymlinks) { throw re(); } - @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { throw re(); } - - @Override protected void setWritable(Path path, boolean writable) { throw re(); } - @Override protected void setExecutable(Path path, boolean executable) { throw re(); } - @Override protected void setReadable(Path path, boolean readable) { throw re(); } - @Override protected void setLastModifiedTime(Path path, long newTime) { throw re(); } - @Override protected void renameTo(Path sourcePath, Path targetPath) { throw re(); } - @Override protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) { - throw re(); - } - @Override protected void createFSDependentHardLink(Path linkPath, Path originalPath) { - throw re(); - } - @Override protected PathFragment readSymbolicLink(Path path) { throw re(); } - @Override protected InputStream getInputStream(Path path) { throw re(); } - @Override protected Collection<Path> getDirectoryEntries(Path path) { throw re(); } - @Override protected OutputStream getOutputStream(Path path, boolean append) { throw re(); } - @Override - protected FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException { - throw re(); - } - } - - protected static final PathFragment SCOPE_ROOT = PathFragment.create("/fs/root"); - - private Path fileLink; - private PathFragment fileLinkTarget; - private Path dirLink; - private PathFragment dirLinkTarget; - - @Before - public final void createLinks() throws Exception { - Preconditions.checkState( - testFS instanceof ScopeEscapableFileSystem, "Not ScopeEscapable: %s", testFS); - ((ScopeEscapableFileSystem) testFS).enableScopeChecking(false); - for (int i = 1; i <= SCOPE_ROOT.segmentCount(); i++) { - testFS.getPath(SCOPE_ROOT.subFragment(0, i)).createDirectory(); - } - - fileLink = testFS.getPath(SCOPE_ROOT.getRelative("link")); - fileLinkTarget = PathFragment.create("/should/be/delegated/fileLinkTarget"); - testFS.createSymbolicLink(fileLink, fileLinkTarget); - - dirLink = testFS.getPath(SCOPE_ROOT.getRelative("dirlink")); - dirLinkTarget = PathFragment.create("/should/be/delegated/dirLinkTarget"); - testFS.createSymbolicLink(dirLink, dirLinkTarget); - } - - /** - * Returns the file system supplied by {@link #getFreshFileSystem}, cast to - * a {@link ScopeEscapableFileSystem}. Also enables scope checking within - * the file system (which we keep disabled for inherited tests that aren't - * intended to test scope boundaries). - */ - private ScopeEscapableFileSystem scopedFS() { - ScopeEscapableFileSystem fs = (ScopeEscapableFileSystem) testFS; - fs.enableScopeChecking(true); - return fs; - } - - // Checks that the semi-resolved path passed to the delegator matches the expected value. - private void checkPath(TestDelegator delegator, PathFragment expectedDelegatedPath) { - assertThat(delegator.lastPath()).isEqualTo(expectedDelegatedPath); - } - - // Asserts that the condition is false and checks that the expected path was delegated. - private void assertFalseWithPathCheck(boolean result, TestDelegator delegator, - PathFragment expectedDelegatedPath) { - assertThat(result).isFalse(); - checkPath(delegator, expectedDelegatedPath); - } - - // Asserts that the condition is true and checks that the expected path was delegated. - private void assertTrueWithPathCheck(boolean result, TestDelegator delegator, - PathFragment expectedDelegatedPath) { - assertThat(result).isTrue(); - checkPath(delegator, expectedDelegatedPath); - } - - ///////////////////////////////////////////////////////////////////////////// - // Tests: - ///////////////////////////////////////////////////////////////////////////// - - @Test - public void testIsReadableCallOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected boolean isReadable(Path path) { - lastPath = path; - return booleanState(); - } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(false); - assertFalseWithPathCheck(fileLink.isReadable(), delegator, fileLinkTarget); - assertFalseWithPathCheck(dirLink.getRelative("a").isReadable(), delegator, - dirLinkTarget.getRelative("a")); - - delegator.setState(true); - assertTrueWithPathCheck(fileLink.isReadable(), delegator, fileLinkTarget); - assertTrueWithPathCheck(dirLink.getRelative("a").isReadable(), delegator, - dirLinkTarget.getRelative("a")); - } - - @Test - public void testIsWritableCallOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected boolean isWritable(Path path) { - lastPath = path; - return booleanState(); - } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(false); - assertFalseWithPathCheck(fileLink.isWritable(), delegator, fileLinkTarget); - assertFalseWithPathCheck(dirLink.getRelative("a").isWritable(), delegator, - dirLinkTarget.getRelative("a")); - - delegator.setState(true); - assertTrueWithPathCheck(fileLink.isWritable(), delegator, fileLinkTarget); - assertTrueWithPathCheck(dirLink.getRelative("a").isWritable(), delegator, - dirLinkTarget.getRelative("a")); - } - - @Test - public void testisExecutableCallOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected boolean isExecutable(Path path) { - lastPath = path; - return booleanState(); - } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(false); - assertFalseWithPathCheck(fileLink.isExecutable(), delegator, fileLinkTarget); - assertFalseWithPathCheck(dirLink.getRelative("a").isExecutable(), delegator, - dirLinkTarget.getRelative("a")); - - delegator.setState(true); - assertTrueWithPathCheck(fileLink.isExecutable(), delegator, fileLinkTarget); - assertTrueWithPathCheck(dirLink.getRelative("a").isExecutable(), delegator, - dirLinkTarget.getRelative("a")); - } - - @Test - public void testIsDirectoryCallOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected boolean isDirectory(Path path, boolean followSymlinks) { - lastPath = path; - return booleanState(); - } - @Override protected boolean exists(Path path, boolean followSymlinks) { return true; } - @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(false); - assertFalseWithPathCheck(fileLink.isDirectory(), delegator, fileLinkTarget); - assertFalseWithPathCheck(dirLink.getRelative("a").isDirectory(), delegator, - dirLinkTarget.getRelative("a")); - - delegator.setState(true); - assertTrueWithPathCheck(fileLink.isDirectory(), delegator, fileLinkTarget); - assertTrueWithPathCheck(dirLink.getRelative("a").isDirectory(), delegator, - dirLinkTarget.getRelative("a")); - } - - @Test - public void testIsFileCallOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected boolean isFile(Path path, boolean followSymlinks) { - lastPath = path; - return booleanState(); - } - @Override protected boolean exists(Path path, boolean followSymlinks) { return true; } - @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(false); - assertFalseWithPathCheck(fileLink.isFile(), delegator, fileLinkTarget); - assertFalseWithPathCheck(dirLink.getRelative("a").isFile(), delegator, - dirLinkTarget.getRelative("a")); - - delegator.setState(true); - assertTrueWithPathCheck(fileLink.isFile(), delegator, fileLinkTarget); - assertTrueWithPathCheck(dirLink.getRelative("a").isFile(), delegator, - dirLinkTarget.getRelative("a")); - } - - @Test - public void testIsSymbolicLinkCallOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected boolean isSymbolicLink(Path path) { - lastPath = path; - return booleanState(); - } - @Override protected boolean exists(Path path, boolean followSymlinks) { return true; } - @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; } - @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; } - }; - scopedFS().setDelegator(delegator); - - // We shouldn't follow final-segment links, so they should never invoke the delegator. - delegator.setState(false); - assertThat(fileLink.isSymbolicLink()).isTrue(); - assertThat(delegator.lastPath()).isNull(); - - assertFalseWithPathCheck(dirLink.getRelative("a").isSymbolicLink(), delegator, - dirLinkTarget.getRelative("a")); - - delegator.setState(true); - assertTrueWithPathCheck(dirLink.getRelative("a").isSymbolicLink(), delegator, - dirLinkTarget.getRelative("a")); - } - - /** - * Returns a test delegator that reflects info passed to Path.exists() calls. - */ - private TestDelegator newExistsDelegator() { - return new TestDelegator() { - @Override protected boolean exists(Path path, boolean followSymlinks) { - lastPath = path; - return booleanState(); - } - @Override protected FileStatus stat(Path path, boolean followSymlinks) throws IOException { - if (!exists(path, followSymlinks)) { - throw new IOException("Expected exception on stat of non-existent file"); - } - return super.stat(path, followSymlinks); - } - @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; } - }; - } - - @Test - public void testExistsCallOnEscapingSymlink() throws Exception { - TestDelegator delegator = newExistsDelegator(); - scopedFS().setDelegator(delegator); - - delegator.setState(false); - assertFalseWithPathCheck(fileLink.exists(), delegator, fileLinkTarget); - assertFalseWithPathCheck(dirLink.getRelative("a").exists(), delegator, - dirLinkTarget.getRelative("a")); - - delegator.setState(true); - assertTrueWithPathCheck(fileLink.exists(), delegator, fileLinkTarget); - assertTrueWithPathCheck(dirLink.getRelative("a").exists(), delegator, - dirLinkTarget.getRelative("a")); - } - - @Test - public void testCreateDirectoryCallOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected boolean createDirectory(Path path) { - lastPath = path; - return booleanState(); - } - @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(false); - assertFalseWithPathCheck(dirLink.getRelative("a").createDirectory(), delegator, - dirLinkTarget.getRelative("a")); - - delegator.setState(true); - assertTrueWithPathCheck(dirLink.getRelative("a").createDirectory(), delegator, - dirLinkTarget.getRelative("a")); - } - - @Test - public void testDeleteCallOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected boolean delete(Path path) { - lastPath = path; - return booleanState(); - } - @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; } - @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(false); - assertThat(fileLink.delete()).isTrue(); - assertThat(delegator.lastPath()).isNull(); // Deleting a link shouldn't require delegation. - assertFalseWithPathCheck(dirLink.getRelative("a").delete(), delegator, - dirLinkTarget.getRelative("a")); - - delegator.setState(true); - assertTrueWithPathCheck(dirLink.getRelative("a").delete(), delegator, - dirLinkTarget.getRelative("a")); - } - - @Test - public void testCallGetFileSizeOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected long getFileSize(Path path, boolean followSymlinks) { - lastPath = path; - return longState(); - } - @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { return 0; } - }; - scopedFS().setDelegator(delegator); - - final int state1 = 10; - delegator.setState(state1); - assertThat(fileLink.getFileSize()).isEqualTo(state1); - checkPath(delegator, fileLinkTarget); - assertThat(dirLink.getRelative("a").getFileSize()).isEqualTo(state1); - checkPath(delegator, dirLinkTarget.getRelative("a")); - - final int state2 = 10; - delegator.setState(state2); - assertThat(fileLink.getFileSize()).isEqualTo(state2); - checkPath(delegator, fileLinkTarget); - assertThat(dirLink.getRelative("a").getFileSize()).isEqualTo(state2); - checkPath(delegator, dirLinkTarget.getRelative("a")); - } - - @Test - public void testCallGetLastModifiedTimeOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected long getLastModifiedTime(Path path, boolean followSymlinks) { - lastPath = path; - return longState(); - } - }; - scopedFS().setDelegator(delegator); - - final int state1 = 10; - delegator.setState(state1); - assertThat(fileLink.getLastModifiedTime()).isEqualTo(state1); - checkPath(delegator, fileLinkTarget); - assertThat(dirLink.getRelative("a").getLastModifiedTime()).isEqualTo(state1); - checkPath(delegator, dirLinkTarget.getRelative("a")); - - final int state2 = 10; - delegator.setState(state2); - assertThat(fileLink.getLastModifiedTime()).isEqualTo(state2); - checkPath(delegator, fileLinkTarget); - assertThat(dirLink.getRelative("a").getLastModifiedTime()).isEqualTo(state2); - checkPath(delegator, dirLinkTarget.getRelative("a")); - } - - @Test - public void testCallSetReadableOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected void setReadable(Path path, boolean readable) { - lastPath = path; - setState(readable); - } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(false); - fileLink.setReadable(true); - assertThat(delegator.booleanState()).isTrue(); - checkPath(delegator, fileLinkTarget); - fileLink.setReadable(false); - assertThat(delegator.booleanState()).isFalse(); - checkPath(delegator, fileLinkTarget); - - delegator.setState(false); - dirLink.getRelative("a").setReadable(true); - assertThat(delegator.booleanState()).isTrue(); - checkPath(delegator, dirLinkTarget.getRelative("a")); - dirLink.getRelative("a").setReadable(false); - assertThat(delegator.booleanState()).isFalse(); - checkPath(delegator, dirLinkTarget.getRelative("a")); - } - - @Test - public void testCallSetWritableOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected void setWritable(Path path, boolean writable) { - lastPath = path; - setState(writable); - } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(false); - fileLink.setWritable(true); - assertThat(delegator.booleanState()).isTrue(); - checkPath(delegator, fileLinkTarget); - fileLink.setWritable(false); - assertThat(delegator.booleanState()).isFalse(); - checkPath(delegator, fileLinkTarget); - - delegator.setState(false); - dirLink.getRelative("a").setWritable(true); - assertThat(delegator.booleanState()).isTrue(); - checkPath(delegator, dirLinkTarget.getRelative("a")); - dirLink.getRelative("a").setWritable(false); - assertThat(delegator.booleanState()).isFalse(); - checkPath(delegator, dirLinkTarget.getRelative("a")); - } - - @Test - public void testCallSetExecutableOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected void setReadable(Path path, boolean readable) { - lastPath = path; - setState(readable); - } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(false); - fileLink.setReadable(true); - assertThat(delegator.booleanState()).isTrue(); - checkPath(delegator, fileLinkTarget); - fileLink.setReadable(false); - assertThat(delegator.booleanState()).isFalse(); - checkPath(delegator, fileLinkTarget); - - delegator.setState(false); - dirLink.getRelative("a").setReadable(true); - assertThat(delegator.booleanState()).isTrue(); - checkPath(delegator, dirLinkTarget.getRelative("a")); - dirLink.getRelative("a").setReadable(false); - assertThat(delegator.booleanState()).isFalse(); - checkPath(delegator, dirLinkTarget.getRelative("a")); - } - - @Test - public void testCallSetLastModifiedTimeOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected void setLastModifiedTime(Path path, long newTime) { - lastPath = path; - setState(newTime); - } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(0); - fileLink.setLastModifiedTime(10); - assertThat(delegator.longState()).isEqualTo(10); - checkPath(delegator, fileLinkTarget); - fileLink.setLastModifiedTime(15); - assertThat(delegator.longState()).isEqualTo(15); - checkPath(delegator, fileLinkTarget); - - dirLink.getRelative("a").setLastModifiedTime(20); - assertThat(delegator.longState()).isEqualTo(20); - checkPath(delegator, dirLinkTarget.getRelative("a")); - dirLink.getRelative("a").setLastModifiedTime(25); - assertThat(delegator.longState()).isEqualTo(25); - checkPath(delegator, dirLinkTarget.getRelative("a")); - } - - @Test - public void testCallRenameToOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected void renameTo(Path sourcePath, Path targetPath) { - lastPath = sourcePath; - setState(targetPath); - } - @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; } - }; - scopedFS().setDelegator(delegator); - - // Renaming a link should work fine. - delegator.setState(null); - fileLink.renameTo(testFS.getPath(SCOPE_ROOT).getRelative("newname")); - assertThat(delegator.lastPath()).isNull(); // Renaming a link shouldn't require delegation. - assertThat(delegator.objectState()).isNull(); - - // Renaming an out-of-scope path to an in-scope path should fail due to filesystem mismatch - // errors. - Path newPath = testFS.getPath(SCOPE_ROOT.getRelative("blah")); - try { - dirLink.getRelative("a").renameTo(newPath); - fail("This is an attempt at a cross-filesystem renaming, which should fail"); - } catch (IOException e) { - // Expected. - } - - // Renaming an out-of-scope path to another out-of-scope path can be valid. - newPath = dirLink.getRelative("b"); - dirLink.getRelative("a").renameTo(newPath); - assertThat(delegator.lastPath()).isEqualTo(dirLinkTarget.getRelative("a")); - assertThat(((Path) delegator.objectState()).asFragment()) - .isEqualTo(dirLinkTarget.getRelative("b")); - } - - @Test - public void testCallCreateSymbolicLinkOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) { - lastPath = linkPath; - setState(targetFragment); - } - @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; } - }; - scopedFS().setDelegator(delegator); - - PathFragment newLinkTarget = PathFragment.create("/something/else"); - dirLink.getRelative("a").createSymbolicLink(newLinkTarget); - assertThat(delegator.lastPath()).isEqualTo(dirLinkTarget.getRelative("a")); - assertThat(delegator.objectState()).isSameAs(newLinkTarget); - } - - @Test - public void testCallReadSymbolicLinkOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected PathFragment readSymbolicLink(Path path) { - lastPath = path; - return (PathFragment) objectState; - } - @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; } - }; - scopedFS().setDelegator(delegator); - - // Since we're not following the link, this shouldn't invoke delegation. - delegator.setState(PathFragment.create("whatever")); - PathFragment p = fileLink.readSymbolicLink(); - assertThat(delegator.lastPath()).isNull(); - assertThat(p).isNotSameAs(delegator.objectState()); - - // This should. - p = dirLink.getRelative("a").readSymbolicLink(); - assertThat(delegator.lastPath()).isEqualTo(dirLinkTarget.getRelative("a")); - assertThat(p).isSameAs(delegator.objectState()); - } - - @Test - public void testCallGetInputStreamOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected InputStream getInputStream(Path path) { - lastPath = path; - return (InputStream) objectState; - } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(new ByteArrayInputStream("blah".getBytes(UTF_8))); - InputStream is = fileLink.getInputStream(); - assertThat(delegator.lastPath()).isEqualTo(fileLinkTarget); - assertThat(is).isSameAs(delegator.objectState()); - - delegator.setState(new ByteArrayInputStream("blah2".getBytes(UTF_8))); - is = dirLink.getInputStream(); - assertThat(delegator.lastPath()).isEqualTo(dirLinkTarget); - assertThat(is).isSameAs(delegator.objectState()); - } - - @Test - public void testCallGetOutputStreamOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected OutputStream getOutputStream(Path path, boolean append) { - lastPath = path; - return (OutputStream) objectState; - } - @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(new ByteArrayOutputStream()); - OutputStream os = fileLink.getOutputStream(); - assertThat(delegator.lastPath()).isEqualTo(fileLinkTarget); - assertThat(os).isSameAs(delegator.objectState()); - - delegator.setState(new ByteArrayOutputStream()); - os = dirLink.getOutputStream(); - assertThat(delegator.lastPath()).isEqualTo(dirLinkTarget); - assertThat(os).isSameAs(delegator.objectState()); - } - - @Test - public void testCallGetDirectoryEntriesOnEscapingSymlink() throws Exception { - TestDelegator delegator = new TestDelegator() { - @Override protected Collection<Path> getDirectoryEntries(Path path) { - lastPath = path; - return ImmutableList.of((Path) objectState); - } - @Override protected boolean isDirectory(Path path, boolean followSymlinks) { return true; } - }; - scopedFS().setDelegator(delegator); - - delegator.setState(testFS.getPath("/anything")); - Collection<Path> entries = dirLink.getDirectoryEntries(); - assertThat(delegator.lastPath()).isEqualTo(dirLinkTarget); - assertThat(entries).hasSize(1); - assertThat(entries.iterator().next()).isSameAs(delegator.objectState()); - } - - /** - * Asserts that "link" is an in-scope link that doesn't result in an out-of-FS - * delegation. If link is relative, its path is relative to SCOPE_ROOT. - * - * Note that we don't actually check that the canonicalized target path matches - * the link's target value. Such testing should be covered by - * SymlinkAwareFileSystemTest. - */ - private void assertInScopeLink(String link, String target, TestDelegator d) throws IOException { - Path l = testFS.getPath(SCOPE_ROOT.getRelative(link)); - testFS.createSymbolicLink(l, PathFragment.create(target)); - l.exists(); - assertThat(d.lastPath()).isNull(); - } - - /** - * Asserts that "link" is an out-of-scope link and that the re-delegated path - * matches expectedPath. If link is relative, its path is relative to SCOPE_ROOT. - */ - private void assertOutOfScopeLink(String link, String target, String expectedPath, - TestDelegator d) throws IOException { - Path l = testFS.getPath(SCOPE_ROOT.getRelative(link)); - testFS.createSymbolicLink(l, PathFragment.create(target)); - l.exists(); - assertThat(d.lastPath().getPathString()).isEqualTo(expectedPath); - } - - /** - * Returns the scope root with the final n segments chopped off (or a 0-segment path - * if n > SCOPE_ROOT.segmentCount()). - */ - private String chopScopeRoot(int n) { - return SCOPE_ROOT - .subFragment(0, n > SCOPE_ROOT.segmentCount() ? 0 : SCOPE_ROOT.segmentCount() - n) - .getPathString(); - } - - /** - * Tests that absolute symlinks with ".." and "." segments are delegated to - * the expected paths. - */ - @Test - public void testAbsoluteSymlinksWithParentReferences() throws Exception { - TestDelegator d = newExistsDelegator(); - scopedFS().setDelegator(d); - testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir"))); - String scopeRoot = SCOPE_ROOT.getPathString(); - String scopeBase = SCOPE_ROOT.getBaseName(); - - // Symlinks that should never escape our scope. - assertInScopeLink("ilink1", scopeRoot, d); - assertInScopeLink("ilink2", scopeRoot + "/target", d); - assertInScopeLink("ilink3", scopeRoot + "/dir/../target", d); - assertInScopeLink("ilink4", scopeRoot + "/dir/../dir/dir2/../target", d); - assertInScopeLink("ilink5", scopeRoot + "/./dir/.././target", d); - assertInScopeLink("ilink6", scopeRoot + "/../" + scopeBase + "/target", d); - assertInScopeLink("ilink7", "/some/path/../.." + scopeRoot + "/target", d); - - // Symlinks that should escape our scope. - assertOutOfScopeLink("olink1", scopeRoot + "/../target", chopScopeRoot(1) + "/target", d); - assertOutOfScopeLink("olink2", "/some/other/path", "/some/other/path", d); - assertOutOfScopeLink("olink3", scopeRoot + "/../target", chopScopeRoot(1) + "/target", d); - assertOutOfScopeLink("olink4", chopScopeRoot(1) + "/target", chopScopeRoot(1) + "/target", d); - assertOutOfScopeLink("olink5", scopeRoot + "/../../../../target", "/target", d); - - // In-scope symlink that's not the final segment in a query. - Path iDirLink = testFS.getPath(SCOPE_ROOT.getRelative("ilinkdir")); - testFS.createSymbolicLink(iDirLink, SCOPE_ROOT.getRelative("dir")); - iDirLink.getRelative("file").exists(); - assertThat(d.lastPath()).isNull(); - - // Out-of-scope symlink that's not the final segment in a query. - Path oDirLink = testFS.getPath(SCOPE_ROOT.getRelative("olinkdir")); - testFS.createSymbolicLink(oDirLink, PathFragment.create("/some/other/dir")); - oDirLink.getRelative("file").exists(); - assertThat(d.lastPath().getPathString()).isEqualTo("/some/other/dir/file"); - } - - /** - * Tests that relative symlinks with ".." and "." segments are delegated to - * the expected paths. - */ - @Test - public void testRelativeSymlinksWithParentReferences() throws Exception { - TestDelegator d = newExistsDelegator(); - scopedFS().setDelegator(d); - testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir"))); - testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2"))); - testFS.createDirectory(testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2/dir3"))); - String scopeRoot = SCOPE_ROOT.getPathString(); - String scopeBase = SCOPE_ROOT.getBaseName(); - - // Symlinks that should never escape our scope. - assertInScopeLink("ilink1", "target", d); - assertInScopeLink("ilink2", "dir/../otherdir/target", d); - assertInScopeLink("dir/ilink3", "../target", d); - assertInScopeLink("dir/dir2/ilink4", "../../target", d); - assertInScopeLink("dir/dir2/ilink5", ".././../dir/./target", d); - assertInScopeLink("dir/dir2/ilink6", "../dir2/../../dir/dir2/dir3/../../../target", d); - - // Symlinks that should escape our scope. - assertOutOfScopeLink("olink1", "../target", chopScopeRoot(1) + "/target", d); - assertOutOfScopeLink("dir/olink2", "../../target", chopScopeRoot(1) + "/target", d); - assertOutOfScopeLink("olink3", "../" + scopeBase + "/target", scopeRoot + "/target", d); - assertOutOfScopeLink("dir/dir2/olink5", "../../../target", chopScopeRoot(1) + "/target", d); - assertOutOfScopeLink("dir/dir2/olink6", "../dir2/../../dir/dir2/../../../target", - chopScopeRoot(1) + "/target", d); - assertOutOfScopeLink("dir/olink7", "../../../target", chopScopeRoot(2) + "target", d); - assertOutOfScopeLink("olink8", "../../../../../target", "/target", d); - - // In-scope symlink that's not the final segment in a query. - Path iDirLink = testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2/ilinkdir")); - testFS.createSymbolicLink(iDirLink, PathFragment.create("../../dir")); - iDirLink.getRelative("file").exists(); - assertThat(d.lastPath()).isNull(); - - // Out-of-scope symlink that's not the final segment in a query. - Path oDirLink = testFS.getPath(SCOPE_ROOT.getRelative("dir/dir2/olinkdir")); - testFS.createSymbolicLink(oDirLink, PathFragment.create("../../../other/dir")); - oDirLink.getRelative("file").exists(); - assertThat(d.lastPath().getPathString()).isEqualTo(chopScopeRoot(1) + "/other/dir/file"); - } -} diff --git a/src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystemTest.java b/src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystemTest.java index 355d96d089..2ebe0a5dd3 100644 --- a/src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystemTest.java +++ b/src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystemTest.java @@ -21,7 +21,7 @@ import com.google.devtools.build.lib.testutil.TestThread; import com.google.devtools.build.lib.vfs.FileSystem; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; -import com.google.devtools.build.lib.vfs.ScopeEscapableFileSystemTest; +import com.google.devtools.build.lib.vfs.SymlinkAwareFileSystemTest; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -34,17 +34,15 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** - * Tests for {@link InMemoryFileSystem}. Note that most tests are inherited - * from {@link ScopeEscapableFileSystemTest} and ancestors. This specific - * file focuses only on concurrency tests. - * + * Tests for {@link InMemoryFileSystem}. Note that most tests are inherited from {@link + * SymlinkAwareFileSystemTest} and ancestors. This specific file focuses only on concurrency tests. */ @RunWith(JUnit4.class) -public class InMemoryFileSystemTest extends ScopeEscapableFileSystemTest { +public class InMemoryFileSystemTest extends SymlinkAwareFileSystemTest { @Override public FileSystem getFreshFileSystem() { - return new InMemoryFileSystem(BlazeClock.instance(), SCOPE_ROOT); + return new InMemoryFileSystem(BlazeClock.instance()); } @Override |