aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystem.java143
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryContentInfo.java25
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystem.java516
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeDirectoryStatus.java123
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/OutOfScopeFileStatus.java110
-rw-r--r--src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/ScopeEscapableStatus.java46
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/ScopeEscapableFileSystemTest.java818
-rw-r--r--src/test/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryFileSystemTest.java12
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