Make FileSystem operate on LocalPath instead of Path. PiperOrigin-RevId: 179082062
diff --git a/src/main/java/com/google/devtools/build/lib/analysis/config/BinTools.java b/src/main/java/com/google/devtools/build/lib/analysis/config/BinTools.java index dc52388..aaac32e 100644 --- a/src/main/java/com/google/devtools/build/lib/analysis/config/BinTools.java +++ b/src/main/java/com/google/devtools/build/lib/analysis/config/BinTools.java
@@ -194,7 +194,7 @@ } private void linkTool(Path sourcePath, Path linkPath) throws ExecException { - if (linkPath.getFileSystem().supportsSymbolicLinksNatively(linkPath)) { + if (linkPath.getFileSystem().supportsSymbolicLinksNatively(linkPath.getLocalPath())) { try { if (!linkPath.isSymbolicLink()) { // ensureSymbolicLink() does not handle the case where there is already
diff --git a/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java b/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java index 7b82dcc..4a79ee1 100644 --- a/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/unix/UnixFileSystem.java
@@ -16,6 +16,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; +import com.google.common.util.concurrent.Striped; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.profiler.Profiler; import com.google.devtools.build.lib.profiler.ProfilerTask; @@ -24,18 +25,19 @@ import com.google.devtools.build.lib.vfs.AbstractFileSystemWithCustomStat; import com.google.devtools.build.lib.vfs.Dirent; 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 com.google.devtools.build.lib.vfs.LocalPath; import java.io.IOException; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import java.util.concurrent.locks.Lock; /** * This class implements the FileSystem interface using direct calls to the UNIX filesystem. */ @ThreadSafe public class UnixFileSystem extends AbstractFileSystemWithCustomStat { + private final Striped<Lock> pathLock = Striped.lock(64); public UnixFileSystem() { } @@ -100,7 +102,7 @@ } @Override - protected Collection<String> getDirectoryEntries(Path path) throws IOException { + protected Collection<String> getDirectoryEntries(LocalPath path) throws IOException { String name = path.getPathString(); String[] entries; long startTime = Profiler.nanoTimeMaybe(); @@ -117,7 +119,7 @@ } @Override - protected PathFragment resolveOneLink(Path path) throws IOException { + protected String resolveOneLink(LocalPath path) throws IOException { // Beware, this seemingly simple code belies the complex specification of // FileSystem.resolveOneLink(). return stat(path, false).isSymbolicLink() @@ -145,7 +147,7 @@ } @Override - protected Collection<Dirent> readdir(Path path, boolean followSymlinks) throws IOException { + protected Collection<Dirent> readdir(LocalPath path, boolean followSymlinks) throws IOException { String name = path.getPathString(); long startTime = Profiler.nanoTimeMaybe(); try { @@ -164,12 +166,12 @@ } @Override - protected FileStatus stat(Path path, boolean followSymlinks) throws IOException { + protected FileStatus stat(LocalPath path, boolean followSymlinks) throws IOException { return statInternal(path, followSymlinks); } @VisibleForTesting - protected UnixFileStatus statInternal(Path path, boolean followSymlinks) throws IOException { + protected UnixFileStatus statInternal(LocalPath path, boolean followSymlinks) throws IOException { String name = path.getPathString(); long startTime = Profiler.nanoTimeMaybe(); try { @@ -185,7 +187,7 @@ // This is a performance optimization in the case where clients // catch and don't re-throw. @Override - protected FileStatus statNullable(Path path, boolean followSymlinks) { + protected FileStatus statNullable(LocalPath path, boolean followSymlinks) { String name = path.getPathString(); long startTime = Profiler.nanoTimeMaybe(); try { @@ -199,16 +201,16 @@ } @Override - protected boolean exists(Path path, boolean followSymlinks) { + protected boolean exists(LocalPath path, boolean followSymlinks) { return statNullable(path, followSymlinks) != null; } /** - * Return true iff the {@code stat} of {@code path} resulted in an {@code ENOENT} - * or {@code ENOTDIR} error. + * Return true iff the {@code stat} of {@code path} resulted in an {@code ENOENT} or {@code + * ENOTDIR} error. */ @Override - protected FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException { + protected FileStatus statIfFound(LocalPath path, boolean followSymlinks) throws IOException { String name = path.getPathString(); long startTime = Profiler.nanoTimeMaybe(); try { @@ -234,29 +236,29 @@ } @Override - protected boolean isReadable(Path path) throws IOException { + protected boolean isReadable(LocalPath path) throws IOException { return (statInternal(path, true).getPermissions() & 0400) != 0; } @Override - protected boolean isWritable(Path path) throws IOException { + protected boolean isWritable(LocalPath path) throws IOException { return (statInternal(path, true).getPermissions() & 0200) != 0; } @Override - protected boolean isExecutable(Path path) throws IOException { + protected boolean isExecutable(LocalPath path) throws IOException { return (statInternal(path, true).getPermissions() & 0100) != 0; } /** - * Adds or remove the bits specified in "permissionBits" to the permission - * mask of the file specified by {@code path}. If the argument {@code add} is - * true, the specified permissions are added, otherwise they are removed. + * Adds or remove the bits specified in "permissionBits" to the permission mask of the file + * specified by {@code path}. If the argument {@code add} is true, the specified permissions are + * added, otherwise they are removed. * * @throws IOException if there was an error writing the file's metadata */ - private void modifyPermissionBits(Path path, int permissionBits, boolean add) - throws IOException { + private void modifyPermissionBits(LocalPath path, int permissionBits, boolean add) + throws IOException { synchronized (path) { int oldMode = statInternal(path, true).getPermissions(); int newMode = add ? (oldMode | permissionBits) : (oldMode & ~permissionBits); @@ -265,39 +267,39 @@ } @Override - protected void setReadable(Path path, boolean readable) throws IOException { + protected void setReadable(LocalPath path, boolean readable) throws IOException { modifyPermissionBits(path, 0400, readable); } @Override - public void setWritable(Path path, boolean writable) throws IOException { + public void setWritable(LocalPath path, boolean writable) throws IOException { modifyPermissionBits(path, 0200, writable); } @Override - protected void setExecutable(Path path, boolean executable) throws IOException { + protected void setExecutable(LocalPath path, boolean executable) throws IOException { modifyPermissionBits(path, 0111, executable); } @Override - protected void chmod(Path path, int mode) throws IOException { + protected void chmod(LocalPath path, int mode) throws IOException { synchronized (path) { NativePosixFiles.chmod(path.toString(), mode); } } @Override - public boolean supportsModifications(Path path) { + public boolean supportsModifications(LocalPath path) { return true; } @Override - public boolean supportsSymbolicLinksNatively(Path path) { + public boolean supportsSymbolicLinksNatively(LocalPath path) { return true; } @Override - public boolean supportsHardLinksNatively(Path path) { + public boolean supportsHardLinksNatively(LocalPath path) { return true; } @@ -307,8 +309,10 @@ } @Override - public boolean createDirectory(Path path) throws IOException { - synchronized (path) { + public boolean createDirectory(LocalPath path) throws IOException { + Lock lock = getPathLock(path); + lock.lock(); + try { // Note: UNIX mkdir(2), FilesystemUtils.mkdir() and createDirectory all // have different ways of representing failure! if (NativePosixFiles.mkdir(path.toString(), 0777)) { @@ -321,25 +325,30 @@ } else { throw new IOException(path + " (File exists)"); } + } finally { + lock.unlock(); } } @Override - protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) - throws IOException { - synchronized (linkPath) { - NativePosixFiles.symlink(targetFragment.toString(), linkPath.toString()); + protected void createSymbolicLink(LocalPath linkPath, String targetFragment) throws IOException { + Lock lock = getPathLock(linkPath); + lock.lock(); + try { + NativePosixFiles.symlink(targetFragment, linkPath.toString()); + } finally { + lock.unlock(); } } @Override - protected PathFragment readSymbolicLink(Path path) throws IOException { + protected String readSymbolicLink(LocalPath path) throws IOException { // Note that the default implementation of readSymbolicLinkUnchecked calls this method and thus // is optimal since we only make one system call in here. String name = path.toString(); long startTime = Profiler.nanoTimeMaybe(); try { - return PathFragment.create(NativePosixFiles.readlink(name)); + return NativePosixFiles.readlink(name); } catch (IOException e) { // EINVAL => not a symbolic link. Anything else is a real error. throw e.getMessage().endsWith("(Invalid argument)") ? new NotASymlinkException(path) : e; @@ -349,38 +358,45 @@ } @Override - public void renameTo(Path sourcePath, Path targetPath) throws IOException { - synchronized (sourcePath) { + public void renameTo(LocalPath sourcePath, LocalPath targetPath) throws IOException { + Lock lock = getPathLock(sourcePath); + lock.lock(); + try { NativePosixFiles.rename(sourcePath.toString(), targetPath.toString()); + } finally { + lock.unlock(); } } @Override - protected long getFileSize(Path path, boolean followSymlinks) throws IOException { + protected long getFileSize(LocalPath path, boolean followSymlinks) throws IOException { return stat(path, followSymlinks).getSize(); } @Override - public boolean delete(Path path) throws IOException { + public boolean delete(LocalPath path) throws IOException { String name = path.toString(); long startTime = Profiler.nanoTimeMaybe(); - synchronized (path) { - try { - return NativePosixFiles.remove(name); - } finally { - profiler.logSimpleTask(startTime, ProfilerTask.VFS_DELETE, name); - } + Lock lock = getPathLock(path); + lock.lock(); + try { + return NativePosixFiles.remove(name); + } finally { + lock.unlock(); + profiler.logSimpleTask(startTime, ProfilerTask.VFS_DELETE, name); } } @Override - protected long getLastModifiedTime(Path path, boolean followSymlinks) throws IOException { + protected long getLastModifiedTime(LocalPath path, boolean followSymlinks) throws IOException { return stat(path, followSymlinks).getLastModifiedTime(); } @Override - public void setLastModifiedTime(Path path, long newTime) throws IOException { - synchronized (path) { + public void setLastModifiedTime(LocalPath path, long newTime) throws IOException { + Lock lock = getPathLock(path); + lock.lock(); + try { if (newTime == -1L) { // "now" NativePosixFiles.utime(path.toString(), true, 0); } else { @@ -388,11 +404,13 @@ int unixTime = (int) (newTime / 1000); NativePosixFiles.utime(path.toString(), false, unixTime); } + } finally { + lock.unlock(); } } @Override - public byte[] getxattr(Path path, String name) throws IOException { + public byte[] getxattr(LocalPath path, String name) throws IOException { String pathName = path.toString(); long startTime = Profiler.nanoTimeMaybe(); try { @@ -407,7 +425,7 @@ } @Override - protected byte[] getDigest(Path path, HashFunction hashFunction) throws IOException { + protected byte[] getDigest(LocalPath path, HashFunction hashFunction) throws IOException { String name = path.toString(); long startTime = Profiler.nanoTimeMaybe(); try { @@ -421,8 +439,13 @@ } @Override - protected void createFSDependentHardLink(Path linkPath, Path originalPath) + protected void createFSDependentHardLink(LocalPath linkPath, LocalPath originalPath) throws IOException { NativePosixFiles.link(originalPath.toString(), linkPath.toString()); } + + /** Returns a per-path lock. The lock is re-entrant. */ + protected Lock getPathLock(LocalPath path) { + return pathLock.get(path); + } }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystem.java index ef1946b..8b9cd53 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystem.java
@@ -37,7 +37,7 @@ } @Override - protected InputStream getInputStream(Path path) throws IOException { + protected InputStream getInputStream(LocalPath path) throws IOException { // This loop is a workaround for an apparent bug in FileInputStream.open, which delegates // ultimately to JVM_Open in the Hotspot JVM. This call is not EINTR-safe, so we must do the // retry here. @@ -55,7 +55,7 @@ } /** Returns either normal or profiled FileInputStream. */ - private InputStream createFileInputStream(Path path) throws FileNotFoundException { + private InputStream createFileInputStream(LocalPath path) throws FileNotFoundException { final String name = path.toString(); if (profiler.isActive() && (profiler.isProfiling(ProfilerTask.VFS_READ) @@ -77,7 +77,7 @@ * Returns either normal or profiled FileOutputStream. Should be used by subclasses to create * default OutputStream instance. */ - protected OutputStream createFileOutputStream(Path path, boolean append) + protected OutputStream createFileOutputStream(LocalPath path, boolean append) throws FileNotFoundException { final String name = path.toString(); if (profiler.isActive() @@ -95,7 +95,7 @@ } @Override - protected OutputStream getOutputStream(Path path, boolean append) throws IOException { + protected OutputStream getOutputStream(LocalPath path, boolean append) throws IOException { synchronized (path) { try { return createFileOutputStream(path, append);
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystemWithCustomStat.java b/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystemWithCustomStat.java index 875df98..b73aa0c 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystemWithCustomStat.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/AbstractFileSystemWithCustomStat.java
@@ -29,30 +29,30 @@ } @Override - protected boolean isFile(Path path, boolean followSymlinks) { + protected boolean isFile(LocalPath path, boolean followSymlinks) { FileStatus stat = statNullable(path, followSymlinks); return stat != null ? stat.isFile() : false; } @Override - protected boolean isSpecialFile(Path path, boolean followSymlinks) { + protected boolean isSpecialFile(LocalPath path, boolean followSymlinks) { FileStatus stat = statNullable(path, followSymlinks); return stat != null ? stat.isSpecialFile() : false; } @Override - protected boolean isSymbolicLink(Path path) { + protected boolean isSymbolicLink(LocalPath path) { FileStatus stat = statNullable(path, false); return stat != null ? stat.isSymbolicLink() : false; } @Override - protected boolean isDirectory(Path path, boolean followSymlinks) { + protected boolean isDirectory(LocalPath path, boolean followSymlinks) { FileStatus stat = statNullable(path, followSymlinks); return stat != null ? stat.isDirectory() : false; } @Override - protected abstract FileStatus stat(Path path, boolean followSymlinks) throws IOException; + protected abstract FileStatus stat(LocalPath path, boolean followSymlinks) throws IOException; }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java index fef88b8..0e92b3d 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystem.java
@@ -39,7 +39,6 @@ */ @ThreadSafe public abstract class FileSystem { - /** Type of hash function to use for digesting files. */ // The underlying HashFunctions are immutable and thread safe. @SuppressWarnings("ImmutableEnumChecker") @@ -100,15 +99,15 @@ public Path getCachedChildPathInternal(Path path, String childName) { return Path.getCachedChildPathInternal(path, childName, /*cacheable=*/ true); } - }; + } } /** * An exception thrown when attempting to resolve an ordinary file as a symlink. */ protected static final class NotASymlinkException extends IOException { - public NotASymlinkException(Path path) { - super(path.toString()); + public NotASymlinkException(LocalPath path) { + super(path.getPathString()); } } @@ -121,24 +120,22 @@ } /** - * Returns an absolute path instance, given an absolute path name, without - * double slashes, .., or . segments. While this method will normalize the - * path representation by creating a structured/parsed representation, it will - * not cause any IO. (e.g., it will not resolve symbolic links if it's a Unix - * file system. + * Returns an absolute path instance, given an absolute path name, without double slashes, .., or + * . segments. While this method will normalize the path representation by creating a + * structured/parsed representation, it will not cause any IO. (e.g., it will not resolve symbolic + * links if it's a Unix file system. */ public Path getPath(String pathName) { return getPath(PathFragment.create(pathName)); } /** - * Returns an absolute path instance, given an absolute path name, without - * double slashes, .., or . segments. While this method will normalize the - * path representation by creating a structured/parsed representation, it will - * not cause any IO. (e.g., it will not resolve symbolic links if it's a Unix - * file system. + * Returns an absolute path instance, given an absolute path name, without double slashes, .., or + * . segments. While this method will normalize the path representation by creating a + * structured/parsed representation, it will not cause any IO. (e.g., it will not resolve symbolic + * links if it's a Unix file system. */ - public Path getPath(PathFragment pathName) { + public final Path getPath(PathFragment pathName) { if (!pathName.isAbsolute()) { throw new IllegalArgumentException(pathName.getPathString() + " (not an absolute path)"); } @@ -165,14 +162,14 @@ * <p>Returns true if FileSystem supports the following: * * <ul> - * <li>{@link #setWritable(Path, boolean)} - * <li>{@link #setExecutable(Path, boolean)} + * <li>{@link #setWritable(LocalPath, boolean)} + * <li>{@link #setExecutable(LocalPath, boolean)} * </ul> * * The above calls will result in an {@link UnsupportedOperationException} on a FileSystem where * this method returns {@code false}. */ - public abstract boolean supportsModifications(Path path); + public abstract boolean supportsModifications(LocalPath path); /** * Returns whether or not the FileSystem supports symbolic links. @@ -180,17 +177,17 @@ * <p>Returns true if FileSystem supports the following: * * <ul> - * <li>{@link #createSymbolicLink(Path, PathFragment)} - * <li>{@link #getFileSize(Path, boolean)} where {@code followSymlinks=false} - * <li>{@link #getLastModifiedTime(Path, boolean)} where {@code followSymlinks=false} - * <li>{@link #readSymbolicLink(Path)} where the link points to a non-existent file + * <li>{@link #createSymbolicLink(LocalPath, String)} + * <li>{@link #getFileSize(LocalPath, boolean)} where {@code followSymlinks=false} + * <li>{@link #getLastModifiedTime(LocalPath, boolean)} where {@code followSymlinks=false} + * <li>{@link #readSymbolicLink(LocalPath)} where the link points to a non-existent file * </ul> * * The above calls may result in an {@link UnsupportedOperationException} on a FileSystem where * this method returns {@code false}. The implementation can try to emulate these calls at its own * discretion. */ - public abstract boolean supportsSymbolicLinksNatively(Path path); + public abstract boolean supportsSymbolicLinksNatively(LocalPath path); /** * Returns whether or not the FileSystem supports hard links. @@ -198,14 +195,14 @@ * <p>Returns true if FileSystem supports the following: * * <ul> - * <li>{@link #createFSDependentHardLink(Path, Path)} + * <li>{@link #createFSDependentHardLink(LocalPath, LocalPath)} * </ul> * * The above calls may result in an {@link UnsupportedOperationException} on a FileSystem where * this method returns {@code false}. The implementation can try to emulate these calls at its own * discretion. */ - protected abstract boolean supportsHardLinksNatively(Path path); + protected abstract boolean supportsHardLinksNatively(LocalPath path); /*** * Returns true if file path is case-sensitive on this file system. Default is true. @@ -215,28 +212,27 @@ /** * Returns the type of the file system path belongs to. * - * <p>The string returned is obtained directly from the operating system, so - * it's a best guess in absence of a guaranteed api. + * <p>The string returned is obtained directly from the operating system, so it's a best guess in + * absence of a guaranteed api. * - * <p>This implementation uses <code>/proc/mounts</code> to determine the - * file system type. + * <p>This implementation uses <code>/proc/mounts</code> to determine the file system type. */ - public String getFileSystemType(Path path) { + public String getFileSystemType(LocalPath path) { String fileSystem = "unknown"; int bestMountPointSegmentCount = -1; try { - Path canonicalPath = path.resolveSymbolicLinks(); - Path mountTable = path.getRelative("/proc/mounts"); - try (InputStreamReader reader = new InputStreamReader(mountTable.getInputStream(), - ISO_8859_1)) { + LocalPath canonicalPath = resolveSymbolicLinks(path); + LocalPath mountTable = path.getRelative("/proc/mounts"); + try (InputStreamReader reader = + new InputStreamReader(getInputStream(mountTable), ISO_8859_1)) { for (String line : CharStreams.readLines(reader)) { String[] words = line.split("\\s+"); if (words.length >= 3) { if (!words[1].startsWith("/")) { continue; } - Path mountPoint = path.getFileSystem().getPath(words[1]); - int segmentCount = mountPoint.asFragment().segmentCount(); + LocalPath mountPoint = LocalPath.create(words[1]); + int segmentCount = mountPoint.split().size(); if (canonicalPath.startsWith(mountPoint) && segmentCount > bestMountPointSegmentCount) { bestMountPointSegmentCount = segmentCount; fileSystem = words[2]; @@ -254,62 +250,62 @@ * Creates a directory with the name of the current path. See {@link Path#createDirectory} for * specification. */ - public abstract boolean createDirectory(Path path) throws IOException; + public abstract boolean createDirectory(LocalPath path) throws IOException; /** * Returns the size in bytes of the file denoted by {@code path}. See {@link * Path#getFileSize(Symlinks)} for specification. * - * <p>Note: for <@link FileSystem>s where {@link #supportsSymbolicLinksNatively(Path)} returns - * false, this method will throw an {@link UnsupportedOperationException} if {@code + * <p>Note: for <@link FileSystem>s where {@link #supportsSymbolicLinksNatively(LocalPath)} + * returns false, this method will throw an {@link UnsupportedOperationException} if {@code * followSymLinks=false}. */ - protected abstract long getFileSize(Path path, boolean followSymlinks) throws IOException; + protected abstract long getFileSize(LocalPath path, boolean followSymlinks) throws IOException; /** Deletes the file denoted by {@code path}. See {@link Path#delete} for specification. */ - public abstract boolean delete(Path path) throws IOException; + public abstract boolean delete(LocalPath path) throws IOException; /** * Returns the last modification time of the file denoted by {@code path}. See {@link * Path#getLastModifiedTime(Symlinks)} for specification. * - * <p>Note: for {@link FileSystem}s where {@link #supportsSymbolicLinksNatively(Path)} returns - * false, this method will throw an {@link UnsupportedOperationException} if {@code + * <p>Note: for {@link FileSystem}s where {@link #supportsSymbolicLinksNatively(LocalPath)} + * returns false, this method will throw an {@link UnsupportedOperationException} if {@code * followSymLinks=false}. */ - protected abstract long getLastModifiedTime(Path path, boolean followSymlinks) throws IOException; + protected abstract long getLastModifiedTime(LocalPath path, boolean followSymlinks) + throws IOException; /** * Sets the last modification time of the file denoted by {@code path}. See {@link * Path#setLastModifiedTime} for specification. */ - public abstract void setLastModifiedTime(Path path, long newTime) throws IOException; + public abstract void setLastModifiedTime(LocalPath path, long newTime) throws IOException; /** - * Returns value of the given extended attribute name or null if attribute - * does not exist or file system does not support extended attributes. Follows symlinks. - * <p>Default implementation assumes that file system does not support - * extended attributes and always returns null. Specific file system - * implementations should override this method if they do provide support - * for extended attributes. + * Returns value of the given extended attribute name or null if attribute does not exist or file + * system does not support extended attributes. Follows symlinks. + * + * <p>Default implementation assumes that file system does not support extended attributes and + * always returns null. Specific file system implementations should override this method if they + * do provide support for extended attributes. * * @param path the file whose extended attribute is to be returned. * @param name the name of the extended attribute key. - * @return the value of the extended attribute associated with 'path', if - * any, or null if no such attribute is defined (ENODATA) or file - * system does not support extended attributes at all. + * @return the value of the extended attribute associated with 'path', if any, or null if no such + * attribute is defined (ENODATA) or file system does not support extended attributes at all. * @throws IOException if the call failed for any other reason. */ - public byte[] getxattr(Path path, String name) throws IOException { + public byte[] getxattr(LocalPath path, String name) throws IOException { return null; } /** - * Gets a fast digest for the given path and hash function type, or {@code null} if there - * isn't one available or the filesystem doesn't support them. This digest should be - * suitable for detecting changes to the file. + * Gets a fast digest for the given path and hash function type, or {@code null} if there isn't + * one available or the filesystem doesn't support them. This digest should be suitable for + * detecting changes to the file. */ - protected byte[] getFastDigest(Path path, HashFunction hashFunction) throws IOException { + protected byte[] getFastDigest(LocalPath path, HashFunction hashFunction) throws IOException { return null; } @@ -318,7 +314,7 @@ * filesystem doesn't support them. This digest should be suitable for detecting changes to the * file. */ - protected final byte[] getFastDigest(Path path) throws IOException { + protected final byte[] getFastDigest(LocalPath path) throws IOException { return getFastDigest(path, digestFunction); } @@ -330,15 +326,14 @@ } /** - * Returns the digest of the file denoted by the path, following - * symbolic links, for the given hash digest function. + * Returns the digest of the file denoted by the path, following symbolic links, for the given + * hash digest function. * * @return a new byte array containing the file's digest * @throws IOException if the digest could not be computed for any reason - * - * Subclasses may (and do) optimize this computation for particular digest functions. + * <p>Subclasses may (and do) optimize this computation for particular digest functions. */ - protected byte[] getDigest(final Path path, HashFunction hashFunction) throws IOException { + protected byte[] getDigest(LocalPath path, HashFunction hashFunction) throws IOException { return new ByteSource() { @Override public InputStream openStream() throws IOException { @@ -353,34 +348,33 @@ * @return a new byte array containing the file's digest * @throws IOException if the digest could not be computed for any reason */ - protected final byte[] getDigest(final Path path) throws IOException { + protected final byte[] getDigest(LocalPath path) throws IOException { return getDigest(path, digestFunction); } /** - * Returns true if "path" denotes an existing symbolic link. See - * {@link Path#isSymbolicLink} for specification. + * Returns true if "path" denotes an existing symbolic link. See {@link Path#isSymbolicLink} for + * specification. */ - protected abstract boolean isSymbolicLink(Path path); + protected abstract boolean isSymbolicLink(LocalPath path); /** - * Appends a single regular path segment 'child' to 'dir', recursively - * resolving symbolic links in 'child'. 'dir' must be canonical. 'maxLinks' is - * the maximum number of symbolic links that may be traversed before it gives - * up (the Linux kernel uses 32). + * Appends a single regular path segment 'child' to 'dir', recursively resolving symbolic links in + * 'child'. 'dir' must be canonical. 'maxLinks' is the maximum number of symbolic links that may + * be traversed before it gives up (the Linux kernel uses 32). * - * <p>(This method does not need to be synchronized; but the result may be - * stale in the case of concurrent modification.) + * <p>(This method does not need to be synchronized; but the result may be stale in the case of + * concurrent modification.) * - * @throws IOException if 'dir' is not an existing directory; or if - * stat(child) fails for any reason, or if 'child' is a symlink and - * readlink(child) fails for any reason (e.g. ENOENT, EACCES), or if - * the chain of symbolic links exceeds 'maxLinks'. + * @throws IOException if 'dir' is not an existing directory; or if stat(child) fails for any + * reason, or if 'child' is a symlink and readlink(child) fails for any reason (e.g. ENOENT, + * EACCES), or if the chain of symbolic links exceeds 'maxLinks'. */ - protected final Path appendSegment(Path dir, String child, int maxLinks) throws IOException { - Path naive = dir.getChild(child); + protected final LocalPath appendSegment(LocalPath dir, String child, int maxLinks) + throws IOException { + LocalPath naive = dir.getRelative(child); - PathFragment linkTarget = resolveOneLink(naive); + String linkTarget = resolveOneLink(naive); if (linkTarget == null) { return naive; // regular file or directory } @@ -388,14 +382,15 @@ if (maxLinks-- == 0) { throw new IOException(naive + " (Too many levels of symbolic links)"); } - if (linkTarget.isAbsolute()) { - dir = getRootDirectory(); + LocalPath linkTargetPath = LocalPath.create(linkTarget); + if (linkTargetPath.isAbsolute()) { + dir = linkTargetPath.getDrive(); } - for (String name : linkTarget.segments()) { + for (String name : linkTargetPath.split()) { if (name.equals(".") || name.isEmpty()) { // no-op } else if (name.equals("..")) { - Path parent = dir.getParentDirectory(); + LocalPath parent = dir.getParentDirectory(); // root's parent is root, when canonicalizing, so this is a no-op. if (parent != null) { dir = parent; } } else { @@ -406,21 +401,19 @@ } /** - * Helper method of {@link #resolveSymbolicLinks(Path)}. This method - * encapsulates the I/O component of a full canonicalization operation. - * Subclasses can (and do) provide more efficient implementations. + * Helper method of {@link #resolveSymbolicLinks(LocalPath)}. This method encapsulates the I/O + * component of a full canonicalization operation. Subclasses can (and do) provide more efficient + * implementations. * - * <p>(This method does not need to be synchronized; but the result may be - * stale in the case of concurrent modification.) + * <p>(This method does not need to be synchronized; but the result may be stale in the case of + * concurrent modification.) * - * @param path a path, of which all but the last segment is guaranteed to be - * canonical - * @return {@link #readSymbolicLink} iff path is a symlink or null iff - * path exists but is not a symlink - * @throws IOException if the file did not exist, or a parent directory could - * not be searched + * @param path a path, of which all but the last segment is guaranteed to be canonical + * @return {@link #readSymbolicLink} iff path is a symlink or null iff path exists but is not a + * symlink + * @throws IOException if the file did not exist, or a parent directory could not be searched */ - protected PathFragment resolveOneLink(Path path) throws IOException { + protected String resolveOneLink(LocalPath path) throws IOException { try { return readSymbolicLink(path); } catch (NotASymlinkException e) { @@ -440,28 +433,25 @@ } /** - * Returns the canonical path for the given path. See - * {@link Path#resolveSymbolicLinks} for specification. + * Returns the canonical path for the given path. See {@link Path#resolveSymbolicLinks} for + * specification. */ - protected Path resolveSymbolicLinks(Path path) - throws IOException { - Path parentNode = path.getParentDirectory(); + protected LocalPath resolveSymbolicLinks(LocalPath path) throws IOException { + LocalPath parentNode = path.getParentDirectory(); return parentNode == null ? path // (root) : appendSegment(resolveSymbolicLinks(parentNode), path.getBaseName(), 32); } /** - * Returns the status of a file. See {@link Path#stat(Symlinks)} for - * specification. + * Returns the status of a file. See {@link Path#stat(Symlinks)} for specification. * - * <p>The default implementation of this method is a "lazy" one, based on - * other accessor methods such as {@link #isFile}, etc. Subclasses may provide - * more efficient specializations. However, we still try to follow Unix-like - * semantics of failing fast in case of non-existent files (or in case of - * permission issues). + * <p>The default implementation of this method is a "lazy" one, based on other accessor methods + * such as {@link #isFile}, etc. Subclasses may provide more efficient specializations. However, + * we still try to follow Unix-like semantics of failing fast in case of non-existent files (or in + * case of permission issues). */ - protected FileStatus stat(final Path path, final boolean followSymlinks) throws IOException { + protected FileStatus stat(LocalPath path, boolean followSymlinks) throws IOException { FileStatus status = new FileStatus() { volatile Boolean isFile; volatile Boolean isDirectory; @@ -526,10 +516,8 @@ return status; } - /** - * Like stat(), but returns null on failures instead of throwing. - */ - protected FileStatus statNullable(Path path, boolean followSymlinks) { + /** Like stat(), but returns null on failures instead of throwing. */ + protected FileStatus statNullable(LocalPath path, boolean followSymlinks) { try { return stat(path, followSymlinks); } catch (IOException e) { @@ -538,12 +526,12 @@ } /** - * Like {@link #stat}, but returns null if the file is not found (corresponding to - * {@code ENOENT} or {@code ENOTDIR} in Unix's stat(2) function) instead of throwing. Note that - * this implementation does <i>not</i> successfully catch {@code ENOTDIR} exceptions. If the + * Like {@link #stat}, but returns null if the file is not found (corresponding to {@code ENOENT} + * or {@code ENOTDIR} in Unix's stat(2) function) instead of throwing. Note that this + * implementation does <i>not</i> successfully catch {@code ENOTDIR} exceptions. If the * instantiated filesystem can catch such errors, it should override this method to do so. */ - protected FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException { + protected FileStatus statIfFound(LocalPath path, boolean followSymlinks) throws IOException { try { return stat(path, followSymlinks); } catch (FileNotFoundException e) { @@ -552,65 +540,65 @@ } /** - * Returns true iff {@code path} denotes an existing directory. See - * {@link Path#isDirectory(Symlinks)} for specification. + * Returns true iff {@code path} denotes an existing directory. See {@link + * Path#isDirectory(Symlinks)} for specification. */ - protected abstract boolean isDirectory(Path path, boolean followSymlinks); + protected abstract boolean isDirectory(LocalPath path, boolean followSymlinks); /** - * Returns true iff {@code path} denotes an existing regular or special file. - * See {@link Path#isFile(Symlinks)} for specification. + * Returns true iff {@code path} denotes an existing regular or special file. See {@link + * Path#isFile(Symlinks)} for specification. */ - protected abstract boolean isFile(Path path, boolean followSymlinks); + protected abstract boolean isFile(LocalPath path, boolean followSymlinks); /** - * Returns true iff {@code path} denotes a special file. - * See {@link Path#isSpecialFile(Symlinks)} for specification. + * Returns true iff {@code path} denotes a special file. See {@link Path#isSpecialFile(Symlinks)} + * for specification. */ - protected abstract boolean isSpecialFile(Path path, boolean followSymlinks); + protected abstract boolean isSpecialFile(LocalPath path, boolean followSymlinks); /** * Creates a symbolic link. See {@link Path#createSymbolicLink(Path)} for specification. * - * <p>Note: for {@link FileSystem}s where {@link #supportsSymbolicLinksNatively(Path)} returns - * false, this method will throw an {@link UnsupportedOperationException} + * <p>Note: for {@link FileSystem}s where {@link #supportsSymbolicLinksNatively(LocalPath)} + * returns false, this method will throw an {@link UnsupportedOperationException} */ - protected abstract void createSymbolicLink(Path linkPath, PathFragment targetFragment) + protected abstract void createSymbolicLink(LocalPath linkPath, String targetFragment) throws IOException; /** * Returns the target of a symbolic link. See {@link Path#readSymbolicLink} for specification. * - * <p>Note: for {@link FileSystem}s where {@link #supportsSymbolicLinksNatively(Path)} returns - * false, this method will throw an {@link UnsupportedOperationException} if the link points to a - * non-existent file. + * <p>Note: for {@link FileSystem}s where {@link #supportsSymbolicLinksNatively(LocalPath)} + * returns false, this method will throw an {@link UnsupportedOperationException} if the link + * points to a non-existent file. * * @throws NotASymlinkException if the current path is not a symbolic link * @throws IOException if the contents of the link could not be read for any reason. */ - protected abstract PathFragment readSymbolicLink(Path path) throws IOException; + protected abstract String readSymbolicLink(LocalPath path) throws IOException; /** * Returns the target of a symbolic link, under the assumption that the given path is indeed a - * symbolic link (this assumption permits efficient implementations). See - * {@link Path#readSymbolicLinkUnchecked} for specification. + * symbolic link (this assumption permits efficient implementations). See {@link + * Path#readSymbolicLinkUnchecked} for specification. * * @throws IOException if the contents of the link could not be read for any reason. */ - protected PathFragment readSymbolicLinkUnchecked(Path path) throws IOException { + protected String readSymbolicLinkUnchecked(LocalPath path) throws IOException { return readSymbolicLink(path); } /** Returns true iff this path denotes an existing file of any kind. Follows symbolic links. */ - public boolean exists(Path path) { + public boolean exists(LocalPath path) { return exists(path, true); } /** - * Returns true iff {@code path} denotes an existing file of any kind. See - * {@link Path#exists(Symlinks)} for specification. + * Returns true iff {@code path} denotes an existing file of any kind. See {@link + * Path#exists(Symlinks)} for specification. */ - protected abstract boolean exists(Path path, boolean followSymlinks); + protected abstract boolean exists(LocalPath path, boolean followSymlinks); /** * Returns a collection containing the names of all entities within the directory denoted by the @@ -618,7 +606,7 @@ * * @throws IOException if there was an error reading the directory entries */ - protected abstract Collection<String> getDirectoryEntries(Path path) throws IOException; + protected abstract Collection<String> getDirectoryEntries(LocalPath path) throws IOException; protected static Dirent.Type direntFromStat(FileStatus stat) { if (stat == null) { @@ -637,19 +625,19 @@ } /** - * Returns a Dirents structure, listing the names of all entries within the - * directory {@code path}, plus their types (file, directory, other). + * Returns a Dirents structure, listing the names of all entries within the directory {@code + * path}, plus their types (file, directory, other). * - * @param followSymlinks whether to follow symlinks when determining the file types of - * individual directory entries. No matter the value of this parameter, symlinks are - * followed when resolving the directory whose entries are to be read. + * @param followSymlinks whether to follow symlinks when determining the file types of individual + * directory entries. No matter the value of this parameter, symlinks are followed when + * resolving the directory whose entries are to be read. * @throws IOException if there was an error reading the directory entries */ - protected Collection<Dirent> readdir(Path path, boolean followSymlinks) throws IOException { + protected Collection<Dirent> readdir(LocalPath path, boolean followSymlinks) throws IOException { Collection<String> children = getDirectoryEntries(path); List<Dirent> dirents = Lists.newArrayListWithCapacity(children.size()); for (String child : children) { - Path childPath = path.getChild(child); + LocalPath childPath = path.getRelative(child); Dirent.Type type = direntFromStat(statNullable(childPath, followSymlinks)); dirents.add(new Dirent(child, type)); } @@ -661,54 +649,54 @@ * * @throws IOException if there was an error reading the file's metadata */ - protected abstract boolean isReadable(Path path) throws IOException; + protected abstract boolean isReadable(LocalPath path) throws IOException; /** * Sets the file to readable (if the argument is true) or non-readable (if the argument is false) * - * <p>Note: for {@link FileSystem}s where {@link #supportsModifications(Path)} returns false or - * which do not support unreadable files, this method will throw an {@link + * <p>Note: for {@link FileSystem}s where {@link #supportsModifications(LocalPath)} returns false + * or which do not support unreadable files, this method will throw an {@link * UnsupportedOperationException}. * * @throws IOException if there was an error reading or writing the file's metadata */ - protected abstract void setReadable(Path path, boolean readable) throws IOException; + protected abstract void setReadable(LocalPath path, boolean readable) throws IOException; /** * Returns true iff the file represented by {@code path} is writable. * * @throws IOException if there was an error reading the file's metadata */ - protected abstract boolean isWritable(Path path) throws IOException; + protected abstract boolean isWritable(LocalPath path) throws IOException; /** * Sets the file to writable (if the argument is true) or non-writable (if the argument is false) * - * <p>Note: for {@link FileSystem}s where {@link #supportsModifications(Path)} returns false, this - * method will throw an {@link UnsupportedOperationException}. + * <p>Note: for {@link FileSystem}s where {@link #supportsModifications(LocalPath)} returns false, + * this method will throw an {@link UnsupportedOperationException}. * * @throws IOException if there was an error reading or writing the file's metadata */ - public abstract void setWritable(Path path, boolean writable) throws IOException; + public abstract void setWritable(LocalPath path, boolean writable) throws IOException; /** * Returns true iff the file represented by the path is executable. * * @throws IOException if there was an error reading the file's metadata */ - protected abstract boolean isExecutable(Path path) throws IOException; + protected abstract boolean isExecutable(LocalPath path) throws IOException; /** * Sets the file to executable, if the argument is true. It is currently not supported to unset * the executable status of a file, so {code executable=false} yields an {@link * UnsupportedOperationException}. * - * <p>Note: for {@link FileSystem}s where {@link #supportsModifications(Path)} returns false, this - * method will throw an {@link UnsupportedOperationException}. + * <p>Note: for {@link FileSystem}s where {@link #supportsModifications(LocalPath)} returns false, + * this method will throw an {@link UnsupportedOperationException}. * * @throws IOException if there was an error reading or writing the file's metadata */ - protected abstract void setExecutable(Path path, boolean executable) throws IOException; + protected abstract void setExecutable(LocalPath path, boolean executable) throws IOException; /** * Sets the file permissions. If permission changes on this {@link FileSystem} are slow (e.g. one @@ -716,12 +704,12 @@ * individually. If this {@link FileSystem} does not support group or others permissions, those * bits will be ignored. * - * <p>Note: for {@link FileSystem}s where {@link #supportsModifications(Path)} returns false, this - * method will throw an {@link UnsupportedOperationException}. + * <p>Note: for {@link FileSystem}s where {@link #supportsModifications(LocalPath)} returns false, + * this method will throw an {@link UnsupportedOperationException}. * * @throws IOException if there was an error reading or writing the file's metadata */ - protected void chmod(Path path, int mode) throws IOException { + protected void chmod(LocalPath path, int mode) throws IOException { setReadable(path, (mode & 0400) != 0); setWritable(path, (mode & 0200) != 0); setExecutable(path, (mode & 0100) != 0); @@ -732,14 +720,14 @@ * * @throws IOException if there was an error opening the file for reading */ - protected abstract InputStream getInputStream(Path path) throws IOException; + protected abstract InputStream getInputStream(LocalPath path) throws IOException; /** * Creates an OutputStream accessing the file denoted by path. * * @throws IOException if there was an error opening the file for writing */ - protected final OutputStream getOutputStream(Path path) throws IOException { + protected final OutputStream getOutputStream(LocalPath path) throws IOException { return getOutputStream(path, false); } @@ -749,13 +737,14 @@ * @param append whether to open the output stream in append mode * @throws IOException if there was an error opening the file for writing */ - protected abstract OutputStream getOutputStream(Path path, boolean append) throws IOException; + protected abstract OutputStream getOutputStream(LocalPath path, boolean append) + throws IOException; /** * Renames the file denoted by "sourceNode" to the location "targetNode". See {@link * Path#renameTo} for specification. */ - public abstract void renameTo(Path sourcePath, Path targetPath) throws IOException; + public abstract void renameTo(LocalPath sourcePath, LocalPath targetPath) throws IOException; /** * Create a new hard link file at "linkPath" for file at "originalPath". @@ -764,9 +753,9 @@ * @param originalPath The path of the original file * @throws IOException if the original file does not exist or the link file already exists */ - protected void createHardLink(Path linkPath, Path originalPath) throws IOException { + protected void createHardLink(LocalPath linkPath, LocalPath originalPath) throws IOException { - if (!originalPath.exists()) { + if (!exists(originalPath, true)) { throw new FileNotFoundException( "File \"" + originalPath.getBaseName() @@ -775,7 +764,7 @@ + "\" does not exist"); } - if (linkPath.exists()) { + if (exists(linkPath, true)) { throw new FileAlreadyExistsException( "New link file \"" + linkPath.getBaseName() + "\" already exists"); } @@ -790,14 +779,13 @@ * @param originalPath The path of the original file * @throws IOException if there was an I/O error */ - protected abstract void createFSDependentHardLink(Path linkPath, Path originalPath) + protected abstract void createFSDependentHardLink(LocalPath linkPath, LocalPath originalPath) throws IOException; /** - * Prefetch all directories and symlinks within the package - * rooted at "path". Enter at most "maxDirs" total directories. - * Specializations for high-latency remote filesystems may wish to + * Prefetch all directories and symlinks within the package rooted at "path". Enter at most + * "maxDirs" total directories. Specializations for high-latency remote filesystems may wish to * implement this in order to warm the filesystem's internal caches. */ - protected void prefetchPackageAsync(Path path, int maxDirs) { } + protected void prefetchPackageAsync(LocalPath path, int maxDirs) {} }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java b/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java index 064e4b2..930a8be 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/FileSystemUtils.java
@@ -378,25 +378,39 @@ } public static ByteSource asByteSource(final Path path) { - return new ByteSource() { - @Override public InputStream openStream() throws IOException { - return path.getInputStream(); - } - }; + return asByteSource(path.getFileSystem(), path.getLocalPath()); } public static ByteSink asByteSink(final Path path, final boolean append) { - return new ByteSink() { - @Override public OutputStream openStream() throws IOException { - return path.getOutputStream(append); - } - }; + return asByteSink(path.getFileSystem(), path.getLocalPath(), append); } public static ByteSink asByteSink(final Path path) { return asByteSink(path, false); } + public static ByteSource asByteSource(FileSystem fileSystem, LocalPath path) { + return new ByteSource() { + @Override + public InputStream openStream() throws IOException { + return fileSystem.getInputStream(path); + } + }; + } + + public static ByteSink asByteSink(FileSystem fileSystem, LocalPath path, final boolean append) { + return new ByteSink() { + @Override + public OutputStream openStream() throws IOException { + return fileSystem.getOutputStream(path, append); + } + }; + } + + public static ByteSink asByteSink(FileSystem fileSystem, LocalPath path) { + return asByteSink(fileSystem, path, false); + } + /** * Copies the file from location "from" to location "to", while overwriting a * potentially existing "to". File's last modified time, executable and @@ -408,18 +422,34 @@ */ @ThreadSafe // but not atomic public static void copyFile(Path from, Path to) throws IOException { + copyFile(from.getFileSystem(), from.getLocalPath(), to.getFileSystem(), to.getLocalPath()); + } + + /** + * Copies the file from location "from" to location "to", while overwriting a potentially existing + * "to". File's last modified time, executable and writable bits are also preserved. + * + * <p>If no error occurs, the method returns normally. If a parent directory does not exist, a + * FileNotFoundException is thrown. An IOException is thrown when other erroneous situations + * occur. (e.g. read errors) + */ + @ThreadSafe // but not atomic + public static void copyFile( + FileSystem fromFileSystem, LocalPath from, FileSystem toFileSystem, LocalPath to) + throws IOException { try { - to.delete(); + toFileSystem.delete(to); } catch (IOException e) { throw new IOException("error copying file: " + "couldn't delete destination: " + e.getMessage()); } - asByteSource(from).copyTo(asByteSink(to)); - to.setLastModifiedTime(from.getLastModifiedTime()); // Preserve mtime. - if (!from.isWritable()) { - to.setWritable(false); // Make file read-only if original was read-only. + asByteSource(fromFileSystem, from).copyTo(asByteSink(toFileSystem, to)); + toFileSystem.setLastModifiedTime( + to, fromFileSystem.getLastModifiedTime(from, true)); // Preserve mtime. + if (!fromFileSystem.isWritable(from)) { + toFileSystem.setWritable(to, false); // Make file read-only if original was read-only. } - to.setExecutable(from.isExecutable()); // Copy executable bit. + toFileSystem.setExecutable(to, fromFileSystem.isExecutable(from)); // Copy executable bit. } /** @@ -665,7 +695,7 @@ if (filesystem instanceof UnionFileSystem) { // If using UnionFS, make sure that we do not traverse filesystem boundaries when creating // parent directories by rehoming the path on the most specific filesystem. - FileSystem delegate = ((UnionFileSystem) filesystem).getDelegate(dir); + FileSystem delegate = ((UnionFileSystem) filesystem).getDelegate(dir.getLocalPath()); dir = delegate.getPath(dir.asFragment()); } @@ -757,7 +787,18 @@ * @throws IOException if there was an error */ public static void writeContentAsLatin1(Path outputFile, String content) throws IOException { - writeContent(outputFile, ISO_8859_1, content); + writeContentAsLatin1(outputFile.getFileSystem(), outputFile.getLocalPath(), content); + } + + /** + * Writes the specified String as ISO-8859-1 (latin1) encoded bytes to the file. Follows symbolic + * links. + * + * @throws IOException if there was an error + */ + public static void writeContentAsLatin1( + FileSystem fileSystem, LocalPath outputFile, String content) throws IOException { + writeContent(fileSystem, outputFile, ISO_8859_1, content); } /** @@ -768,7 +809,18 @@ */ public static void writeContent(Path outputFile, Charset charset, String content) throws IOException { - asByteSink(outputFile).asCharSink(charset).write(content); + writeContent(outputFile.getFileSystem(), outputFile.getLocalPath(), charset, content); + } + + /** + * Writes the specified String using the specified encoding to the file. Follows symbolic links. + * + * @throws IOException if there was an error + */ + public static void writeContent( + FileSystem fileSystem, LocalPath outputFile, Charset charset, String content) + throws IOException { + asByteSink(fileSystem, outputFile).asCharSink(charset).write(content); } /** @@ -977,7 +1029,7 @@ * Returns the type of the file system path belongs to. */ public static String getFileSystem(Path path) { - return path.getFileSystem().getFileSystemType(path); + return path.getFileSystem().getFileSystemType(path.getLocalPath()); } /**
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java index dca8b95..1381388 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/JavaIoFileSystem.java
@@ -14,6 +14,7 @@ package com.google.devtools.build.lib.vfs; import com.google.common.annotations.VisibleForTesting; +import com.google.common.util.concurrent.Striped; import com.google.devtools.build.lib.clock.Clock; import com.google.devtools.build.lib.clock.JavaClock; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; @@ -28,6 +29,7 @@ import java.nio.file.attribute.BasicFileAttributes; import java.util.ArrayList; import java.util.Collection; +import java.util.concurrent.locks.Lock; /** * A FileSystem that does not use any JNI and hence, does not require a shared library be present at @@ -39,6 +41,7 @@ */ @ThreadSafe public class JavaIoFileSystem extends AbstractFileSystemWithCustomStat { + private final Striped<Lock> pathLock = Striped.lock(64); private static final LinkOption[] NO_LINK_OPTION = new LinkOption[0]; // This isn't generally safe; we rely on the file system APIs not modifying the array. private static final LinkOption[] NOFOLLOW_LINKS_OPTION = @@ -66,7 +69,7 @@ this.clock = clock; } - protected File getIoFile(Path path) { + protected File getIoFile(LocalPath path) { return new File(path.toString()); } @@ -78,7 +81,7 @@ * avoids extra allocations and does not lose track of the underlying Java filesystem, which is * useful for some in-memory filesystem implementations like JimFS. */ - protected java.nio.file.Path getNioPath(Path path) { + protected java.nio.file.Path getNioPath(LocalPath path) { return Paths.get(path.toString()); } @@ -87,7 +90,7 @@ } @Override - protected Collection<String> getDirectoryEntries(Path path) throws IOException { + protected Collection<String> getDirectoryEntries(LocalPath path) throws IOException { File file = getIoFile(path); String[] entries = null; long startTime = Profiler.nanoTimeMaybe(); @@ -113,7 +116,7 @@ } @Override - protected boolean exists(Path path, boolean followSymlinks) { + protected boolean exists(LocalPath path, boolean followSymlinks) { java.nio.file.Path nioPath = getNioPath(path); long startTime = Profiler.nanoTimeMaybe(); try { @@ -124,7 +127,7 @@ } @Override - protected boolean isReadable(Path path) throws IOException { + protected boolean isReadable(LocalPath path) throws IOException { File file = getIoFile(path); long startTime = Profiler.nanoTimeMaybe(); try { @@ -138,7 +141,7 @@ } @Override - protected boolean isWritable(Path path) throws IOException { + protected boolean isWritable(LocalPath path) throws IOException { File file = getIoFile(path); long startTime = Profiler.nanoTimeMaybe(); try { @@ -156,7 +159,7 @@ } @Override - protected boolean isExecutable(Path path) throws IOException { + protected boolean isExecutable(LocalPath path) throws IOException { File file = getIoFile(path); long startTime = Profiler.nanoTimeMaybe(); try { @@ -170,7 +173,7 @@ } @Override - protected void setReadable(Path path, boolean readable) throws IOException { + protected void setReadable(LocalPath path, boolean readable) throws IOException { File file = getIoFile(path); if (!file.exists()) { throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); @@ -179,7 +182,7 @@ } @Override - public void setWritable(Path path, boolean writable) throws IOException { + public void setWritable(LocalPath path, boolean writable) throws IOException { File file = getIoFile(path); if (!file.exists()) { throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); @@ -188,7 +191,7 @@ } @Override - protected void setExecutable(Path path, boolean executable) throws IOException { + protected void setExecutable(LocalPath path, boolean executable) throws IOException { File file = getIoFile(path); if (!file.exists()) { throw new FileNotFoundException(path + ERR_NO_SUCH_FILE_OR_DIR); @@ -197,17 +200,17 @@ } @Override - public boolean supportsModifications(Path path) { + public boolean supportsModifications(LocalPath path) { return true; } @Override - public boolean supportsSymbolicLinksNatively(Path path) { + public boolean supportsSymbolicLinksNatively(LocalPath path) { return true; } @Override - public boolean supportsHardLinksNatively(Path path) { + public boolean supportsHardLinksNatively(LocalPath path) { return true; } @@ -217,22 +220,26 @@ } @Override - public boolean createDirectory(Path path) throws IOException { + public boolean createDirectory(LocalPath path) throws IOException { // We always synchronize on the current path before doing it on the parent path and file system // path structure ensures that this locking order will never be reversed. // When refactoring, check that subclasses still work as expected and there can be no // deadlocks. - synchronized (path) { + Lock lock = getPathLock(path); + lock.lock(); + try { File file = getIoFile(path); if (file.mkdir()) { return true; } // We will be checking the state of the parent path as well. Synchronize on it before - // attempting anything. - Path parentDirectory = path.getParentDirectory(); - synchronized (parentDirectory) { + // attempting anything. The striped lock used is re-entrant so this is safe. + LocalPath parentDirectory = path.getParentDirectory(); + Lock parentLock = getPathLock(parentDirectory); + parentLock.lock(); + try { if (fileIsSymbolicLink(file)) { throw new IOException(path + ERR_FILE_EXISTS); } @@ -254,7 +261,11 @@ // Parent exists, is writable, yet we can't create our directory. throw new FileNotFoundException(path.getParentDirectory() + ERR_NOT_A_DIRECTORY); } + } finally { + parentLock.unlock(); } + } finally { + lock.unlock(); } } @@ -277,11 +288,10 @@ } @Override - protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) - throws IOException { + protected void createSymbolicLink(LocalPath linkPath, String targetFragment) throws IOException { java.nio.file.Path nioPath = getNioPath(linkPath); try { - Files.createSymbolicLink(nioPath, Paths.get(targetFragment.getPathString())); + Files.createSymbolicLink(nioPath, Paths.get(targetFragment)); } catch (java.nio.file.FileAlreadyExistsException e) { throw new IOException(linkPath + ERR_FILE_EXISTS); } catch (java.nio.file.AccessDeniedException e) { @@ -292,12 +302,12 @@ } @Override - protected PathFragment readSymbolicLink(Path path) throws IOException { + protected String readSymbolicLink(LocalPath path) throws IOException { java.nio.file.Path nioPath = getNioPath(path); long startTime = Profiler.nanoTimeMaybe(); try { String link = Files.readSymbolicLink(nioPath).toString(); - return PathFragment.create(link); + return link; } catch (java.nio.file.NotLinkException e) { throw new NotASymlinkException(path); } catch (java.nio.file.NoSuchFileException e) { @@ -308,8 +318,10 @@ } @Override - public void renameTo(Path sourcePath, Path targetPath) throws IOException { - synchronized (sourcePath) { + public void renameTo(LocalPath sourcePath, LocalPath targetPath) throws IOException { + Lock lock = getPathLock(sourcePath); + lock.lock(); + try { File sourceFile = getIoFile(sourcePath); File targetFile = getIoFile(targetPath); if (!sourceFile.renameTo(targetFile)) { @@ -330,11 +342,13 @@ throw new FileAccessException(sourcePath + " -> " + targetPath + ERR_PERMISSION_DENIED); } } + } finally { + lock.unlock(); } } @Override - protected long getFileSize(Path path, boolean followSymlinks) throws IOException { + protected long getFileSize(LocalPath path, boolean followSymlinks) throws IOException { long startTime = Profiler.nanoTimeMaybe(); try { return stat(path, followSymlinks).getSize(); @@ -344,7 +358,7 @@ } @Override - public boolean delete(Path path) throws IOException { + public boolean delete(LocalPath path) throws IOException { File file = getIoFile(path); long startTime = Profiler.nanoTimeMaybe(); synchronized (path) { @@ -367,7 +381,7 @@ } @Override - protected long getLastModifiedTime(Path path, boolean followSymlinks) throws IOException { + protected long getLastModifiedTime(LocalPath path, boolean followSymlinks) throws IOException { File file = getIoFile(path); long startTime = Profiler.nanoTimeMaybe(); try { @@ -382,7 +396,7 @@ } @Override - public void setLastModifiedTime(Path path, long newTime) throws IOException { + public void setLastModifiedTime(LocalPath path, long newTime) throws IOException { File file = getIoFile(path); if (!file.setLastModified(newTime == -1L ? clock.currentTimeMillis() : newTime)) { if (!file.exists()) { @@ -396,7 +410,7 @@ } @Override - protected byte[] getDigest(Path path, HashFunction hashFunction) throws IOException { + protected byte[] getDigest(LocalPath path, HashFunction hashFunction) throws IOException { String name = path.toString(); long startTime = Profiler.nanoTimeMaybe(); try { @@ -407,17 +421,15 @@ } /** - * Returns the status of a file. See {@link Path#stat(Symlinks)} for - * specification. + * Returns the status of a file. See {@link Path#stat(Symlinks)} for specification. * - * <p>The default implementation of this method is a "lazy" one, based on - * other accessor methods such as {@link #isFile}, etc. Subclasses may provide - * more efficient specializations. However, we still try to follow Unix-like - * semantics of failing fast in case of non-existent files (or in case of - * permission issues). + * <p>The default implementation of this method is a "lazy" one, based on other accessor methods + * such as {@link #isFile}, etc. Subclasses may provide more efficient specializations. However, + * we still try to follow Unix-like semantics of failing fast in case of non-existent files (or in + * case of permission issues). */ @Override - protected FileStatus stat(final Path path, final boolean followSymlinks) throws IOException { + protected FileStatus stat(LocalPath path, final boolean followSymlinks) throws IOException { java.nio.file.Path nioPath = getNioPath(path); final BasicFileAttributes attributes; try { @@ -474,7 +486,7 @@ } @Override - protected FileStatus statIfFound(Path path, boolean followSymlinks) { + protected FileStatus statIfFound(LocalPath path, boolean followSymlinks) { try { return stat(path, followSymlinks); } catch (FileNotFoundException e) { @@ -491,10 +503,15 @@ } @Override - protected void createFSDependentHardLink(Path linkPath, Path originalPath) + protected void createFSDependentHardLink(LocalPath linkPath, LocalPath originalPath) throws IOException { Files.createLink( java.nio.file.Paths.get(linkPath.toString()), java.nio.file.Paths.get(originalPath.toString())); } + + /** Returns a per-path lock. The lock is re-entrant. */ + protected Lock getPathLock(LocalPath path) { + return pathLock.get(path); + } }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java b/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java index a32a4e3..b05c89f 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/LocalPath.java
@@ -15,12 +15,14 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; import com.google.devtools.build.lib.util.OS; import com.google.devtools.build.lib.windows.WindowsShortPath; import com.google.devtools.build.lib.windows.jni.WindowsFileOperations; import java.io.IOException; -import java.util.Arrays; -import java.util.concurrent.atomic.AtomicReference; +import java.util.List; +import java.util.regex.Pattern; import javax.annotation.Nullable; /** @@ -50,6 +52,8 @@ public static final LocalPath EMPTY = create(""); + private static final Splitter PATH_SPLITTER = Splitter.on('/').omitEmptyStrings(); + private final String path; private final int driveStrLength; // 0 for relative paths, 1 on Unix, 3 on Windows private final OsPathPolicy os; @@ -228,19 +232,33 @@ * Splits a path into its constituent parts. The root is not included. This is an inefficient * operation and should be avoided. */ - public String[] split() { - String[] segments = path.split("/"); - if (driveStrLength > 0) { - // String#split("/") for some reason returns a zero-length array - // String#split("/hello") returns a 2-length array, so this makes little sense - if (segments.length == 0) { - return segments; - } - return Arrays.copyOfRange(segments, 1, segments.length); + public List<String> split() { + List<String> segments = PATH_SPLITTER.splitToList(path); + if (driveStrLength > 1) { + return segments.subList(1, segments.size()); } return segments; } + /** Returns the drive of this local path, eg. "/" on Unix or "C:/" on Windows. */ + public LocalPath getDrive() { + if (driveStrLength == 0) { + throw new IllegalArgumentException("Cannot get mount of non-absolute path."); + } + return new LocalPath(path.substring(0, driveStrLength), driveStrLength, os); + } + + /** + * Returns whether this is the root of the entire file system. + * + * <p>Please avoid this method. On Unix, this corresponds to the '/' mount point. Windows drives + * (C:/) do not have a parent and are not the root of the entire file system, so do not return + * true. + */ + public boolean isRoot() { + return os.isRoot(this); + } + /** * Returns whether this path is an ancestor of another path. * @@ -339,10 +357,14 @@ char getSeparator(); boolean isCaseSensitive(); + + boolean isRoot(LocalPath localPath); } @VisibleForTesting static class UnixOsPathPolicy implements OsPathPolicy { + private static Splitter UNIX_PATH_SPLITTER = + Splitter.on(Pattern.compile("/+")).omitEmptyStrings(); @Override public int needsToNormalize(String path) { @@ -362,7 +384,7 @@ dotCount = c == '.' ? dotCount + 1 : 0; prevChar = c; } - if (prevChar == '/' || dotCount == 1 || dotCount == 2) { + if ((n > 1 && prevChar == '/') || dotCount == 1 || dotCount == 2) { return NEEDS_NORMALIZE; } return NORMALIZED; @@ -377,8 +399,8 @@ return path; } boolean isAbsolute = path.charAt(0) == '/'; - String[] segments = path.split("/+"); - int segmentCount = removeRelativePaths(segments, isAbsolute ? 1 : 0); + String[] segments = Iterables.toArray(UNIX_PATH_SPLITTER.split(path), String.class); + int segmentCount = removeRelativePaths(segments, 0); StringBuilder sb = new StringBuilder(path.length()); if (isAbsolute) { sb.append('/'); @@ -425,6 +447,11 @@ public boolean isCaseSensitive() { return true; } + + @Override + public boolean isRoot(LocalPath localPath) { + return localPath.path.equals("/"); + } } /** Mac is a unix file system that is case insensitive. */ @@ -451,8 +478,9 @@ private static final int NEEDS_SHORT_PATH_NORMALIZATION = NEEDS_NORMALIZE + 1; - // msys root, used to resolve paths from msys starting with "/" - private static final AtomicReference<String> UNIX_ROOT = new AtomicReference<>(null); + private static Splitter WINDOWS_PATH_SPLITTER = + Splitter.on(Pattern.compile("[\\\\/]+")).omitEmptyStrings(); + private final ShortPathResolver shortPathResolver; interface ShortPathResolver { @@ -482,10 +510,6 @@ public int needsToNormalize(String path) { int n = path.length(); int normalizationLevel = 0; - // Check for unix path - if (n > 0 && path.charAt(0) == '/') { - normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE); - } int dotCount = 0; char prevChar = 0; int segmentBeginIndex = 0; // The start index of the current path index @@ -517,7 +541,7 @@ dotCount = c == '.' ? dotCount + 1 : 0; prevChar = c; } - if (prevChar == '/' || dotCount == 1 || dotCount == 2) { + if ((n > 1 && prevChar == '/') || dotCount == 1 || dotCount == 2) { normalizationLevel = Math.max(normalizationLevel, NEEDS_NORMALIZE); } return normalizationLevel; @@ -534,26 +558,19 @@ path = resolvedPath; } } - String[] segments = path.split("[\\\\/]+"); + String[] segments = Iterables.toArray(WINDOWS_PATH_SPLITTER.splitToList(path), String.class); int driveStrLength = getDriveStrLength(path); boolean isAbsolute = driveStrLength > 0; - int segmentSkipCount = isAbsolute ? 1 : 0; + int segmentSkipCount = isAbsolute && driveStrLength > 1 ? 1 : 0; StringBuilder sb = new StringBuilder(path.length()); if (isAbsolute) { - char driveLetter = path.charAt(0); - sb.append(Character.toUpperCase(driveLetter)); - sb.append(":/"); - } - // unix path support - if (!path.isEmpty() && path.charAt(0) == '/') { - if (path.length() == 2 || (path.length() > 2 && path.charAt(2) == '/')) { - sb.append(Character.toUpperCase(path.charAt(1))); - sb.append(":/"); - segmentSkipCount = 2; + char c = path.charAt(0); + if (c == '/') { + sb.append('/'); } else { - String unixRoot = getUnixRoot(); - sb.append(unixRoot); + sb.append(Character.toUpperCase(c)); + sb.append(":/"); } } int segmentCount = removeRelativePaths(segments, segmentSkipCount); @@ -570,6 +587,12 @@ @Override public int getDriveStrLength(String path) { int n = path.length(); + if (n == 0) { + return 0; + } + if (path.charAt(0) == '/') { + return 1; + } if (n < 3) { return 0; } @@ -622,44 +645,10 @@ return false; } - private String getUnixRoot() { - String value = UNIX_ROOT.get(); - if (value == null) { - String jvmFlag = "bazel.windows_unix_root"; - value = determineUnixRoot(jvmFlag); - if (value == null) { - throw new IllegalStateException( - String.format( - "\"%1$s\" JVM flag is not set. Use the --host_jvm_args flag or export the " - + "BAZEL_SH environment variable. For example " - + "\"--host_jvm_args=-D%1$s=c:/tools/msys64\" or " - + "\"set BAZEL_SH=c:/tools/msys64/usr/bin/bash.exe\".", - jvmFlag)); - } - if (getDriveStrLength(value) != 3) { - throw new IllegalStateException( - String.format("\"%s\" must be an absolute path, got: \"%s\"", jvmFlag, value)); - } - value = value.replace('\\', '/'); - if (value.length() > 3 && value.endsWith("/")) { - value = value.substring(0, value.length() - 1); - } - UNIX_ROOT.set(value); - } - return value; - } - - private String determineUnixRoot(String jvmArgName) { - // Get the path from a JVM flag, if specified. - String path = System.getProperty(jvmArgName); - if (path == null) { - return null; - } - path = path.trim(); - if (path.isEmpty()) { - return null; - } - return path; + @Override + public boolean isRoot(LocalPath localPath) { + // Return true for Unix paths for testing + return localPath.path.equals("/"); } } @@ -685,7 +674,8 @@ private static int removeRelativePaths(String[] segments, int starti) { int segmentCount = 0; int shift = starti; - for (int i = starti; i < segments.length; ++i) { + int n = segments.length; + for (int i = starti; i < n; ++i) { String segment = segments[i]; switch (segment) { case ".":
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/Path.java b/src/main/java/com/google/devtools/build/lib/vfs/Path.java index 1d3947d..b09b713 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/Path.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/Path.java
@@ -39,7 +39,11 @@ import java.util.Objects; /** - * Instances of this class represent pathnames, forming a tree structure to implement sharing of + * NOTE: This class is superseded by {@link LocalPath}. You should prefer storing simple strings / + * path fragments, then converting to a {@link LocalPath} only when you need to do local file system + * access. A migration is underway. + * + * <p>Instances of this class represent pathnames, forming a tree structure to implement sharing of * common prefixes (parent directory names). A node in these trees is something like foo, bar, .., * ., or /. If the instance is not a root path, it will have a parent path. A path can also have * children, which are indexed by name in a map. @@ -423,6 +427,10 @@ return builder.toString(); } + public LocalPath getLocalPath() { + return LocalPath.create(getPathString()); + } + @Override public void repr(SkylarkPrinter printer) { printer.append(getPathString()); @@ -500,7 +508,7 @@ * Path. */ public boolean exists(FileSystem fileSystem) { - return fileSystem.exists(this, true); + return fileSystem.exists(this.getLocalPath(), true); } /** Prefer to use {@link #exists(FileSystem, Symlinks)}. */ @@ -519,7 +527,7 @@ * link is dereferenced until a file other than a symbolic link is found */ public boolean exists(FileSystem fileSystem, Symlinks followSymlinks) { - return fileSystem.exists(this, followSymlinks.toBoolean()); + return fileSystem.exists(this.getLocalPath(), followSymlinks.toBoolean()); } /** Prefer to use {@link #getDirectoryEntries(FileSystem)}. */ @@ -540,7 +548,7 @@ */ public Collection<Path> getDirectoryEntries(FileSystem fileSystem) throws IOException, FileNotFoundException { - Collection<String> entries = fileSystem.getDirectoryEntries(this); + Collection<String> entries = fileSystem.getDirectoryEntries(this.getLocalPath()); Collection<Path> result = new ArrayList<>(entries.size()); for (String entry : entries) { result.add(getChild(entry)); @@ -568,7 +576,7 @@ */ public Collection<Dirent> readdir(FileSystem fileSystem, Symlinks followSymlinks) throws IOException { - return fileSystem.readdir(this, followSymlinks.toBoolean()); + return fileSystem.readdir(this.getLocalPath(), followSymlinks.toBoolean()); } /** Prefer to use {@link #stat(FileSystem)}. */ @@ -588,7 +596,7 @@ * {@code FileStatus} are called. */ public FileStatus stat(FileSystem fileSystem) throws IOException { - return fileSystem.stat(this, true); + return fileSystem.stat(this.getLocalPath(), true); } /** Prefer to use {@link #statNullable(FileSystem)}. */ @@ -620,7 +628,7 @@ * Path. */ public FileStatus statNullable(FileSystem fileSystem, Symlinks symlinks) { - return fileSystem.statNullable(this, symlinks.toBoolean()); + return fileSystem.statNullable(this.getLocalPath(), symlinks.toBoolean()); } /** Prefer to use {@link #stat(FileSystem, Symlinks)}. */ @@ -642,7 +650,7 @@ * {@code FileStatus} are called */ public FileStatus stat(FileSystem fileSystem, Symlinks followSymlinks) throws IOException { - return fileSystem.stat(this, followSymlinks.toBoolean()); + return fileSystem.stat(this.getLocalPath(), followSymlinks.toBoolean()); } /** Prefer to use {@link #statIfFound(FileSystem)}. */ @@ -660,7 +668,7 @@ * Path. */ public FileStatus statIfFound(FileSystem fileSystem) throws IOException { - return fileSystem.statIfFound(this, true); + return fileSystem.statIfFound(this.getLocalPath(), true); } /** Prefer to use {@link #statIfFound(FileSystem, Symlinks)}. */ @@ -680,7 +688,7 @@ * link is dereferenced until a file other than a symbolic link is found */ public FileStatus statIfFound(FileSystem fileSystem, Symlinks followSymlinks) throws IOException { - return fileSystem.statIfFound(this, followSymlinks.toBoolean()); + return fileSystem.statIfFound(this.getLocalPath(), followSymlinks.toBoolean()); } /** Prefer to use {@link #isDirectory()} (FileSystem)}. */ @@ -696,7 +704,7 @@ * Path. */ public boolean isDirectory(FileSystem fileSystem) { - return fileSystem.isDirectory(this, true); + return fileSystem.isDirectory(this.getLocalPath(), true); } /** Prefer to use {@link #isDirectory(FileSystem, Symlinks)}. */ @@ -715,7 +723,7 @@ * link is dereferenced until a file other than a symbolic link is found */ public boolean isDirectory(FileSystem fileSystem, Symlinks followSymlinks) { - return fileSystem.isDirectory(this, followSymlinks.toBoolean()); + return fileSystem.isDirectory(this.getLocalPath(), followSymlinks.toBoolean()); } /** Prefer to use {@link #isFile(FileSystem)}. */ @@ -734,7 +742,7 @@ * it excludes symbolic links and directories. */ public boolean isFile(FileSystem fileSystem) { - return fileSystem.isFile(this, true); + return fileSystem.isFile(this.getLocalPath(), true); } /** Prefer to use {@link #isFile(FileSystem, Symlinks)}. */ @@ -756,7 +764,7 @@ * link is dereferenced until a file other than a symbolic link is found. */ public boolean isFile(FileSystem fileSystem, Symlinks followSymlinks) { - return fileSystem.isFile(this, followSymlinks.toBoolean()); + return fileSystem.isFile(this.getLocalPath(), followSymlinks.toBoolean()); } /** Prefer to use {@link #isSpecialFile(FileSystem)}. */ @@ -773,7 +781,7 @@ * Path. */ public boolean isSpecialFile(FileSystem fileSystem) { - return fileSystem.isSpecialFile(this, true); + return fileSystem.isSpecialFile(this.getLocalPath(), true); } /** Prefer to use {@link #isSpecialFile(FileSystem, Symlinks)}. */ @@ -792,7 +800,7 @@ * link is dereferenced until a path other than a symbolic link is found. */ public boolean isSpecialFile(FileSystem fileSystem, Symlinks followSymlinks) { - return fileSystem.isSpecialFile(this, followSymlinks.toBoolean()); + return fileSystem.isSpecialFile(this.getLocalPath(), followSymlinks.toBoolean()); } /** Prefer to use {@link #isSymbolicLink(FileSystem)}. */ @@ -808,7 +816,7 @@ * Path. */ public boolean isSymbolicLink(FileSystem fileSystem) { - return fileSystem.isSymbolicLink(this); + return fileSystem.isSymbolicLink(this.getLocalPath()); } /** @@ -1002,7 +1010,7 @@ */ public OutputStream getOutputStream(FileSystem fileSystem, boolean append) throws IOException, FileNotFoundException { - return fileSystem.getOutputStream(this, append); + return fileSystem.getOutputStream(this.getLocalPath(), append); } /** Prefer to use {@link #createDirectory(FileSystem)}. */ @@ -1023,7 +1031,7 @@ * @throws IOException if the directory creation failed for any reason */ public boolean createDirectory(FileSystem fileSystem) throws IOException { - return fileSystem.createDirectory(this); + return fileSystem.createDirectory(this.getLocalPath()); } /** Prefer to use {@link #createSymbolicLink(FileSystem, Path)}. */ @@ -1044,7 +1052,7 @@ */ public void createSymbolicLink(FileSystem fileSystem, Path target) throws IOException { checkSameFilesystem(target); - fileSystem.createSymbolicLink(this, target.asFragment()); + fileSystem.createSymbolicLink(this.getLocalPath(), target.asFragment().getPathString()); } /** Prefer to use {@link #createSymbolicLink(FileSystem, PathFragment)}. */ @@ -1064,7 +1072,7 @@ * @throws IOException if the creation of the symbolic link was unsuccessful for any reason */ public void createSymbolicLink(FileSystem fileSystem, PathFragment target) throws IOException { - fileSystem.createSymbolicLink(this, target); + fileSystem.createSymbolicLink(this.getLocalPath(), target.getPathString()); } /** Prefer to use {@link #readSymbolicLink(FileSystem)}. */ @@ -1089,7 +1097,7 @@ * could not be read for any reason */ public PathFragment readSymbolicLink(FileSystem fileSystem) throws IOException { - return fileSystem.readSymbolicLink(this); + return PathFragment.create(fileSystem.readSymbolicLink(this.getLocalPath())); } /** Prefer to use {@link #readSymbolicLinkUnchecked(FileSystem)}. */ @@ -1110,7 +1118,7 @@ * could not be read for any reason */ public PathFragment readSymbolicLinkUnchecked(FileSystem fileSystem) throws IOException { - return fileSystem.readSymbolicLinkUnchecked(this); + return PathFragment.create(fileSystem.readSymbolicLinkUnchecked(this.getLocalPath())); } /** Prefer to use {@link #createHardLink(FileSystem, Path)}. */ @@ -1129,7 +1137,7 @@ * @throws IOException if there was an error executing {@link FileSystem#createHardLink} */ public void createHardLink(FileSystem fileSystem, Path link) throws IOException { - fileSystem.createHardLink(link, this); + fileSystem.createHardLink(link.getLocalPath(), this.getLocalPath()); } /** Prefer to use {@link #resolveSymbolicLinks(FileSystem)}. */ @@ -1150,7 +1158,7 @@ * example, the path does not exist) */ public Path resolveSymbolicLinks(FileSystem fileSystem) throws IOException { - return fileSystem.resolveSymbolicLinks(this); + return fileSystem.getPath(fileSystem.resolveSymbolicLinks(this.getLocalPath()).getPathString()); } /** Prefer to use {@link #renameTo(FileSystem, Path)}. */ @@ -1173,7 +1181,7 @@ */ public void renameTo(FileSystem fileSystem, Path target) throws IOException { checkSameFilesystem(target); - fileSystem.renameTo(this, target); + fileSystem.renameTo(this.getLocalPath(), target.getLocalPath()); } /** Prefer to use {@link #getFileSize(FileSystem)}. */ @@ -1194,7 +1202,7 @@ * @throws IOException if the file's metadata could not be read, or some other error occurred */ public long getFileSize(FileSystem fileSystem) throws IOException, FileNotFoundException { - return fileSystem.getFileSize(this, true); + return fileSystem.getFileSize(this.getLocalPath(), true); } /** Prefer to use {@link #getFileSize(FileSystem, Symlinks)}. */ @@ -1219,7 +1227,7 @@ */ public long getFileSize(FileSystem fileSystem, Symlinks followSymlinks) throws IOException, FileNotFoundException { - return fileSystem.getFileSize(this, followSymlinks.toBoolean()); + return fileSystem.getFileSize(this.getLocalPath(), followSymlinks.toBoolean()); } /** Prefer to use {@link #delete(FileSystem)}. */ @@ -1240,7 +1248,7 @@ * @throws IOException if the deletion failed but the file was present prior to the call */ public boolean delete(FileSystem fileSystem) throws IOException { - return fileSystem.delete(this); + return fileSystem.delete(this.getLocalPath()); } /** Prefer to use {@link #getLastModifiedTime(FileSystem)}. */ @@ -1262,7 +1270,7 @@ * @throws IOException if the operation failed for any reason */ public long getLastModifiedTime(FileSystem fileSystem) throws IOException { - return fileSystem.getLastModifiedTime(this, true); + return fileSystem.getLastModifiedTime(this.getLocalPath(), true); } /** Prefer to use {@link #getLastModifiedTime(FileSystem, Symlinks)}. */ @@ -1287,7 +1295,7 @@ */ public long getLastModifiedTime(FileSystem fileSystem, Symlinks followSymlinks) throws IOException { - return fileSystem.getLastModifiedTime(this, followSymlinks.toBoolean()); + return fileSystem.getLastModifiedTime(this.getLocalPath(), followSymlinks.toBoolean()); } /** Prefer to use {@link #setLastModifiedTime(FileSystem, long)}. */ @@ -1312,7 +1320,7 @@ * @throws IOException if the modification time for the file could not be set for any reason */ public void setLastModifiedTime(FileSystem fileSystem, long newTime) throws IOException { - fileSystem.setLastModifiedTime(this, newTime); + fileSystem.setLastModifiedTime(this.getLocalPath(), newTime); } /** Prefer to use {@link #getxattr(FileSystem, String)}. */ @@ -1329,7 +1337,7 @@ * Path. */ public byte[] getxattr(FileSystem fileSystem, String name) throws IOException { - return fileSystem.getxattr(this, name); + return fileSystem.getxattr(this.getLocalPath(), name); } /** Prefer to use {@link #getFastDigest(FileSystem)}. */ @@ -1346,7 +1354,7 @@ * Path. */ public byte[] getFastDigest(FileSystem fileSystem) throws IOException { - return fileSystem.getFastDigest(this); + return fileSystem.getFastDigest(this.getLocalPath()); } /** Prefer to use {@link #isValidDigest(FileSystem, byte[])}. */ @@ -1381,7 +1389,7 @@ * @throws IOException if the digest could not be computed for any reason */ public byte[] getDigest(FileSystem fileSystem) throws IOException { - return fileSystem.getDigest(this); + return fileSystem.getDigest(this.getLocalPath()); } /** Prefer to use {@link #getDigest(FileSystem, HashFunction)}. */ @@ -1401,7 +1409,7 @@ * @throws IOException if the digest could not be computed for any reason */ public byte[] getDigest(FileSystem fileSystem, HashFunction hashFunction) throws IOException { - return fileSystem.getDigest(this, hashFunction); + return fileSystem.getDigest(this.getLocalPath(), hashFunction); } /** Prefer to use {@link #getInputStream(FileSystem)}. */ @@ -1420,7 +1428,7 @@ * @throws IOException if the file was not found or could not be opened for reading */ public InputStream getInputStream(FileSystem fileSystem) throws IOException { - return fileSystem.getInputStream(this); + return fileSystem.getInputStream(this.getLocalPath()); } /** @@ -1450,7 +1458,7 @@ * encountered, or the file's metadata could not be read */ public boolean isWritable(FileSystem fileSystem) throws IOException, FileNotFoundException { - return fileSystem.isWritable(this); + return fileSystem.isWritable(this.getLocalPath()); } /** Prefer to use {@link #setReadable(FileSystem, boolean)}. */ @@ -1472,7 +1480,7 @@ */ public void setReadable(FileSystem fileSystem, boolean readable) throws IOException, FileNotFoundException { - fileSystem.setReadable(this, readable); + fileSystem.setReadable(this.getLocalPath(), readable); } /** Prefer to use {@link #setWritable(FileSystem, boolean)}. */ @@ -1496,7 +1504,7 @@ */ public void setWritable(FileSystem fileSystem, boolean writable) throws IOException, FileNotFoundException { - fileSystem.setWritable(this, writable); + fileSystem.setWritable(this.getLocalPath(), writable); } /** Prefer to use {@link #isExecutable(FileSystem)}. */ @@ -1517,7 +1525,7 @@ * @throws IOException if some other I/O error occurred */ public boolean isExecutable(FileSystem fileSystem) throws IOException, FileNotFoundException { - return fileSystem.isExecutable(this); + return fileSystem.isExecutable(this.getLocalPath()); } /** Prefer to use {@link #isReadable(FileSystem)}. */ @@ -1538,7 +1546,7 @@ * @throws IOException if some other I/O error occurred */ public boolean isReadable(FileSystem fileSystem) throws IOException, FileNotFoundException { - return fileSystem.isReadable(this); + return fileSystem.isReadable(this.getLocalPath()); } /** Prefer to use {@link #setExecutable(FileSystem, boolean)}. */ @@ -1560,7 +1568,7 @@ */ public void setExecutable(FileSystem fileSystem, boolean executable) throws IOException, FileNotFoundException { - fileSystem.setExecutable(this, executable); + fileSystem.setExecutable(this.getLocalPath(), executable); } /** Prefer to use {@link #chmod(FileSystem, int)}. */ @@ -1583,7 +1591,7 @@ * @throws IOException if the metadata change failed, for example because of permissions */ public void chmod(FileSystem fileSystem, int mode) throws IOException { - fileSystem.chmod(this, mode); + fileSystem.chmod(this.getLocalPath(), mode); } /** Prefer to use {@link #prefetchPackageAsync(FileSystem, int)}. */ @@ -1593,7 +1601,7 @@ } public void prefetchPackageAsync(FileSystem fileSystem, int maxDirs) { - fileSystem.prefetchPackageAsync(this, maxDirs); + fileSystem.prefetchPackageAsync(this.getLocalPath(), maxDirs); } /**
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/PathTrie.java b/src/main/java/com/google/devtools/build/lib/vfs/PathTrie.java deleted file mode 100644 index fd783c5..0000000 --- a/src/main/java/com/google/devtools/build/lib/vfs/PathTrie.java +++ /dev/null
@@ -1,82 +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.base.Preconditions; -import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadCompatible; -import java.util.HashMap; -import java.util.Map; - -/** - * A trie that operates on path segments. - * - * @param <T> the type of the values. - */ -@ThreadCompatible -public class PathTrie<T> { - @SuppressWarnings("unchecked") - private static class Node<T> { - private Node() { - children = new HashMap<>(); - } - - private T value; - private Map<String, Node<T>> children; - } - - private final Node<T> root; - - public PathTrie() { - root = new Node<T>(); - } - - /** - * Puts a value in the trie. - * - * @param key must be an absolute path. - */ - public void put(PathFragment key, T value) { - Preconditions.checkArgument(key.isAbsolute(), "PathTrie only accepts absolute paths as keys."); - Node<T> current = root; - for (String segment : key.getSegments()) { - current.children.putIfAbsent(segment, new Node<T>()); - current = current.children.get(segment); - } - current.value = value; - } - - /** - * Gets a value from the trie. If there is an entry with the same key, that will be returned, - * otherwise, the value corresponding to the key that matches the longest prefix of the input. - */ - public T get(PathFragment key) { - Node<T> current = root; - T lastValue = current.value; - - for (String segment : key.getSegments()) { - if (current.children.containsKey(segment)) { - current = current.children.get(segment); - // Track the values of increasing matching prefixes. - if (current.value != null) { - lastValue = current.value; - } - } else { - // We've reached the longest prefix, no further to go. - break; - } - } - - return lastValue; - } -}
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java index bfcc4f9..0eab17c 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystem.java
@@ -17,20 +17,21 @@ import java.io.OutputStream; /** - * An abstract partial implementation of FileSystem for read-only - * implementations. + * An abstract partial implementation of FileSystem for read-only implementations. * * <p>Any ReadonlyFileSystem does not support the following: + * * <ul> - * <li>{@link #createDirectory(Path)}</li> - * <li>{@link #createSymbolicLink(Path, PathFragment)}</li> - * <li>{@link #delete(Path)}</li> - * <li>{@link #getOutputStream(Path)}</li> - * <li>{@link #renameTo(Path, Path)}</li> - * <li>{@link #setExecutable(Path, boolean)}</li> - * <li>{@link #setLastModifiedTime(Path, long)}</li> - * <li>{@link #setWritable(Path, boolean)}</li> + * <li>{@link #createDirectory(LocalPath)} + * <li>{@link #createSymbolicLink(LocalPath, String)} + * <li>{@link #delete(LocalPath)} + * <li>{@link #getOutputStream(LocalPath)} + * <li>{@link #renameTo(LocalPath, LocalPath)} + * <li>{@link #setExecutable(LocalPath, boolean)} + * <li>{@link #setLastModifiedTime(LocalPath, long)} + * <li>{@link #setWritable(LocalPath, boolean)} * </ul> + * * The above calls will always result in an {@link IOException}. */ public abstract class ReadonlyFileSystem extends AbstractFileSystem { @@ -46,37 +47,37 @@ } @Override - protected OutputStream getOutputStream(Path path, boolean append) throws IOException { + protected OutputStream getOutputStream(LocalPath path, boolean append) throws IOException { throw modificationException(); } @Override - protected void setReadable(Path path, boolean readable) throws IOException { + protected void setReadable(LocalPath path, boolean readable) throws IOException { throw modificationException(); } @Override - public void setWritable(Path path, boolean writable) throws IOException { + public void setWritable(LocalPath path, boolean writable) throws IOException { throw modificationException(); } @Override - protected void setExecutable(Path path, boolean executable) { + protected void setExecutable(LocalPath path, boolean executable) { throw new UnsupportedOperationException("setExecutable"); } @Override - public boolean supportsModifications(Path path) { + public boolean supportsModifications(LocalPath path) { return false; } @Override - public boolean supportsSymbolicLinksNatively(Path path) { + public boolean supportsSymbolicLinksNatively(LocalPath path) { return false; } @Override - public boolean supportsHardLinksNatively(Path path) { + public boolean supportsHardLinksNatively(LocalPath path) { return false; } @@ -86,32 +87,32 @@ } @Override - public boolean createDirectory(Path path) throws IOException { + public boolean createDirectory(LocalPath path) throws IOException { throw modificationException(); } @Override - protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException { + protected void createSymbolicLink(LocalPath linkPath, String targetFragment) throws IOException { throw modificationException(); } @Override - public void renameTo(Path sourcePath, Path targetPath) throws IOException { + public void renameTo(LocalPath sourcePath, LocalPath targetPath) throws IOException { throw modificationException(); } @Override - public boolean delete(Path path) throws IOException { + public boolean delete(LocalPath path) throws IOException { throw modificationException(); } @Override - public void setLastModifiedTime(Path path, long newTime) throws IOException { + public void setLastModifiedTime(LocalPath path, long newTime) throws IOException { throw modificationException(); } @Override - protected void createFSDependentHardLink(Path linkPath, Path originalPath) + protected void createFSDependentHardLink(LocalPath linkPath, LocalPath originalPath) throws IOException { throw modificationException(); }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java index de5daca..46e058c 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/ReadonlyFileSystemWithCustomStat.java
@@ -31,37 +31,37 @@ } @Override - protected OutputStream getOutputStream(Path path, boolean append) throws IOException { + protected OutputStream getOutputStream(LocalPath path, boolean append) throws IOException { throw modificationException(); } @Override - protected void setReadable(Path path, boolean readable) throws IOException { + protected void setReadable(LocalPath path, boolean readable) throws IOException { throw modificationException(); } @Override - public void setWritable(Path path, boolean writable) throws IOException { + public void setWritable(LocalPath path, boolean writable) throws IOException { throw modificationException(); } @Override - protected void setExecutable(Path path, boolean executable) { + protected void setExecutable(LocalPath path, boolean executable) { throw new UnsupportedOperationException("setExecutable"); } @Override - public boolean supportsModifications(Path path) { + public boolean supportsModifications(LocalPath path) { return false; } @Override - public boolean supportsSymbolicLinksNatively(Path path) { + public boolean supportsSymbolicLinksNatively(LocalPath path) { return false; } @Override - public boolean supportsHardLinksNatively(Path path) { + public boolean supportsHardLinksNatively(LocalPath path) { return false; } @@ -71,33 +71,33 @@ } @Override - public boolean createDirectory(Path path) throws IOException { + public boolean createDirectory(LocalPath path) throws IOException { throw modificationException(); } @Override - protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException { + protected void createSymbolicLink(LocalPath linkPath, String targetFragment) throws IOException { throw modificationException(); } @Override - protected void createFSDependentHardLink(Path linkPath, Path originalPath) + protected void createFSDependentHardLink(LocalPath linkPath, LocalPath originalPath) throws IOException { throw modificationException(); } @Override - public void renameTo(Path sourcePath, Path targetPath) throws IOException { + public void renameTo(LocalPath sourcePath, LocalPath targetPath) throws IOException { throw modificationException(); } @Override - public boolean delete(Path path) throws IOException { + public boolean delete(LocalPath path) throws IOException { throw modificationException(); } @Override - public void setLastModifiedTime(Path path, long newTime) throws IOException { + public void setLastModifiedTime(LocalPath path, long newTime) throws IOException { throw modificationException(); } }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java index 824e71d..504aa937 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/UnionFileSystem.java
@@ -15,12 +15,14 @@ package com.google.devtools.build.lib.vfs; import com.google.common.base.Preconditions; -import com.google.common.collect.Lists; import com.google.devtools.build.lib.concurrent.ThreadSafety; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.util.ArrayList; import java.util.Collection; +import java.util.Comparator; +import java.util.List; import java.util.Map; import javax.annotation.Nullable; @@ -42,12 +44,21 @@ * are not currently supported. */ @ThreadSafety.ThreadSafe -public class UnionFileSystem extends FileSystem { +public final class UnionFileSystem extends FileSystem { - // Prefix trie index, allowing children to easily inherit prefix mappings - // of their parents. - // This does not currently handle unicode filenames. - private final PathTrie<FileSystem> pathDelegate; + private static class FileSystemAndPrefix { + final LocalPath prefix; + final FileSystem fileSystem; + + public FileSystemAndPrefix(LocalPath prefix, FileSystem fileSystem) { + this.prefix = prefix; + this.fileSystem = fileSystem; + } + } + + // List of file systems and their mappings, sorted by prefix length descending. + private final List<FileSystemAndPrefix> fileSystems; + private final FileSystem rootFileSystem; // True if the file path is case-sensitive on all the FileSystem // or False if they are all case-insensitive, otherwise error. @@ -59,30 +70,35 @@ * @param prefixMapping map of path prefixes to {@link FileSystem}s * @param rootFileSystem root for default requests; i.e. mapping of "/" */ - public UnionFileSystem(Map<PathFragment, FileSystem> prefixMapping, FileSystem rootFileSystem) { + public UnionFileSystem(Map<LocalPath, FileSystem> prefixMapping, FileSystem rootFileSystem) { super(); Preconditions.checkNotNull(prefixMapping); Preconditions.checkNotNull(rootFileSystem); Preconditions.checkArgument(rootFileSystem != this, "Circular root filesystem."); Preconditions.checkArgument( - !prefixMapping.containsKey(PathFragment.EMPTY_FRAGMENT), + !prefixMapping.containsKey(LocalPath.EMPTY), "Attempted to specify an explicit root prefix mapping; " + "please use the rootFileSystem argument instead."); - this.pathDelegate = new PathTrie<>(); + this.fileSystems = new ArrayList<>(); + this.rootFileSystem = rootFileSystem; this.isCaseSensitive = rootFileSystem.isFilePathCaseSensitive(); - for (Map.Entry<PathFragment, FileSystem> prefix : prefixMapping.entrySet()) { + for (Map.Entry<LocalPath, FileSystem> prefix : prefixMapping.entrySet()) { FileSystem delegate = prefix.getValue(); Preconditions.checkArgument( delegate.isFilePathCaseSensitive() == this.isCaseSensitive, "The case sensitiveness of FileSystem are different in UnionFileSystem"); - PathFragment prefixPath = prefix.getKey(); + LocalPath prefixPath = prefix.getKey(); // Extra slash prevents within-directory mappings, which Path can't handle. - pathDelegate.put(prefixPath, delegate); + fileSystems.add(new FileSystemAndPrefix(prefixPath, delegate)); } - pathDelegate.put(PathFragment.ROOT_FRAGMENT, rootFileSystem); + // Order by length descending. This ensures that more specific mapping takes precedence + // when we try to find the file system of a given path. + Comparator<FileSystemAndPrefix> comparator = + Comparator.comparing(f -> f.prefix.getPathString().length()); + fileSystems.sort(comparator.reversed()); } /** @@ -92,19 +108,24 @@ * @param path the {@link Path} to map to a filesystem * @throws IllegalArgumentException if no delegate exists for the path */ - protected FileSystem getDelegate(Path path) { + FileSystem getDelegate(LocalPath path) { Preconditions.checkNotNull(path); - FileSystem immediateDelegate = pathDelegate.get(path.asFragment()); - - // Should never actually happen if the root delegate is present. - Preconditions.checkNotNull(immediateDelegate, "No delegate filesystem exists for %s", path); - return immediateDelegate; + FileSystem delegate = null; + // Linearly iterate over each mapped file system and find the one that handles this path. + // For small number of mappings, this will be more efficient than using a trie + for (FileSystemAndPrefix fileSystemAndPrefix : this.fileSystems) { + if (path.startsWith(fileSystemAndPrefix.prefix)) { + delegate = fileSystemAndPrefix.fileSystem; + break; + } + } + return delegate != null ? delegate : rootFileSystem; } // Associates the path with the root of the given delegate filesystem. // Necessary to avoid null pointer problems inside of the delegates. - protected Path adjustPath(Path path, FileSystem delegate) { - return delegate.getPath(path.asFragment()); + LocalPath adjustPath(LocalPath path, FileSystem delegate) { + return path; } /** @@ -114,20 +135,20 @@ * @param path {@link Path} to the symbolic link */ @Override - protected PathFragment readSymbolicLink(Path path) throws IOException { + protected String readSymbolicLink(LocalPath path) throws IOException { Preconditions.checkNotNull(path); FileSystem delegate = getDelegate(path); return delegate.readSymbolicLink(adjustPath(path, delegate)); } @Override - protected PathFragment resolveOneLink(Path path) throws IOException { + protected String resolveOneLink(LocalPath path) throws IOException { Preconditions.checkNotNull(path); FileSystem delegate = getDelegate(path); return delegate.resolveOneLink(adjustPath(path, delegate)); } - private void checkModifiable(Path path) { + private void checkModifiable(LocalPath path) { if (!supportsModifications(path)) { throw new UnsupportedOperationException( String.format("Modifications to this %s are disabled.", getClass().getSimpleName())); @@ -135,21 +156,21 @@ } @Override - public boolean supportsModifications(Path path) { + public boolean supportsModifications(LocalPath path) { FileSystem delegate = getDelegate(path); path = adjustPath(path, delegate); return delegate.supportsModifications(path); } @Override - public boolean supportsSymbolicLinksNatively(Path path) { + public boolean supportsSymbolicLinksNatively(LocalPath path) { FileSystem delegate = getDelegate(path); path = adjustPath(path, delegate); return delegate.supportsSymbolicLinksNatively(path); } @Override - public boolean supportsHardLinksNatively(Path path) { + public boolean supportsHardLinksNatively(LocalPath path) { FileSystem delegate = getDelegate(path); path = adjustPath(path, delegate); return delegate.supportsHardLinksNatively(path); @@ -161,7 +182,7 @@ } @Override - public String getFileSystemType(Path path) { + public String getFileSystemType(LocalPath path) { try { path = internalResolveSymlink(path); } catch (IOException e) { @@ -172,14 +193,14 @@ } @Override - protected byte[] getDigest(Path path, HashFunction hashFunction) throws IOException { + protected byte[] getDigest(LocalPath path, HashFunction hashFunction) throws IOException { path = internalResolveSymlink(path); FileSystem delegate = getDelegate(path); return delegate.getDigest(adjustPath(path, delegate), hashFunction); } @Override - public boolean createDirectory(Path path) throws IOException { + public boolean createDirectory(LocalPath path) throws IOException { checkModifiable(path); // When creating the exact directory that is mapped, // create it on both the parent's delegate and the path's delegate. @@ -192,7 +213,7 @@ // ls / ("foo" would be missing if not created on the parent) // ls /foo (would fail if foo weren't also present on the child) FileSystem delegate = getDelegate(path); - Path parent = path.getParentDirectory(); + LocalPath parent = path.getParentDirectory(); if (parent != null) { parent = internalResolveSymlink(parent); FileSystem parentDelegate = getDelegate(parent); @@ -206,28 +227,28 @@ } @Override - protected long getFileSize(Path path, boolean followSymlinks) throws IOException { + protected long getFileSize(LocalPath path, boolean followSymlinks) throws IOException { path = followSymlinks ? internalResolveSymlink(path) : path; FileSystem delegate = getDelegate(path); return delegate.getFileSize(adjustPath(path, delegate), false); } @Override - public boolean delete(Path path) throws IOException { + public boolean delete(LocalPath path) throws IOException { checkModifiable(path); FileSystem delegate = getDelegate(path); return delegate.delete(adjustPath(path, delegate)); } @Override - protected long getLastModifiedTime(Path path, boolean followSymlinks) throws IOException { + protected long getLastModifiedTime(LocalPath path, boolean followSymlinks) throws IOException { path = followSymlinks ? internalResolveSymlink(path) : path; FileSystem delegate = getDelegate(path); return delegate.getLastModifiedTime(adjustPath(path, delegate), false); } @Override - public void setLastModifiedTime(Path path, long newTime) throws IOException { + public void setLastModifiedTime(LocalPath path, long newTime) throws IOException { path = internalResolveSymlink(path); checkModifiable(path); FileSystem delegate = getDelegate(path); @@ -235,14 +256,14 @@ } @Override - protected boolean isSymbolicLink(Path path) { + protected boolean isSymbolicLink(LocalPath path) { FileSystem delegate = getDelegate(path); path = adjustPath(path, delegate); return delegate.isSymbolicLink(path); } @Override - protected boolean isDirectory(Path path, boolean followSymlinks) { + protected boolean isDirectory(LocalPath path, boolean followSymlinks) { try { path = followSymlinks ? internalResolveSymlink(path) : path; } catch (IOException e) { @@ -253,7 +274,7 @@ } @Override - protected boolean isFile(Path path, boolean followSymlinks) { + protected boolean isFile(LocalPath path, boolean followSymlinks) { try { path = followSymlinks ? internalResolveSymlink(path) : path; } catch (IOException e) { @@ -264,7 +285,7 @@ } @Override - protected boolean isSpecialFile(Path path, boolean followSymlinks) { + protected boolean isSpecialFile(LocalPath path, boolean followSymlinks) { try { path = followSymlinks ? internalResolveSymlink(path) : path; } catch (IOException e) { @@ -275,7 +296,7 @@ } @Override - protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException { + protected void createSymbolicLink(LocalPath linkPath, String targetFragment) throws IOException { checkModifiable(linkPath); if (!supportsSymbolicLinksNatively(linkPath)) { throw new UnsupportedOperationException( @@ -287,7 +308,7 @@ } @Override - protected boolean exists(Path path, boolean followSymlinks) { + protected boolean exists(LocalPath path, boolean followSymlinks) { try { path = followSymlinks ? internalResolveSymlink(path) : path; } catch (IOException e) { @@ -298,7 +319,7 @@ } @Override - protected FileStatus stat(Path path, boolean followSymlinks) throws IOException { + protected FileStatus stat(LocalPath path, boolean followSymlinks) throws IOException { path = followSymlinks ? internalResolveSymlink(path) : path; FileSystem delegate = getDelegate(path); return delegate.stat(adjustPath(path, delegate), false); @@ -308,7 +329,7 @@ // UnixFileSystem implements statNullable and stat as separate codepaths. // More generally, we wish to delegate all filesystem operations. @Override - protected FileStatus statNullable(Path path, boolean followSymlinks) { + protected FileStatus statNullable(LocalPath path, boolean followSymlinks) { try { path = followSymlinks ? internalResolveSymlink(path) : path; } catch (IOException e) { @@ -320,7 +341,7 @@ @Override @Nullable - protected FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException { + protected FileStatus statIfFound(LocalPath path, boolean followSymlinks) throws IOException { path = followSymlinks ? internalResolveSymlink(path) : path; FileSystem delegate = getDelegate(path); return delegate.statIfFound(adjustPath(path, delegate), false); @@ -333,35 +354,30 @@ * @param path the {@link Path} whose children are to be retrieved */ @Override - protected Collection<String> getDirectoryEntries(Path path) throws IOException { + protected Collection<String> getDirectoryEntries(LocalPath path) throws IOException { path = internalResolveSymlink(path); FileSystem delegate = getDelegate(path); - Path resolvedPath = adjustPath(path, delegate); - Collection<Path> entries = resolvedPath.getDirectoryEntries(); - Collection<String> result = Lists.newArrayListWithCapacity(entries.size()); - for (Path entry : entries) { - result.add(entry.getBaseName()); - } - return result; + LocalPath resolvedPath = adjustPath(path, delegate); + return delegate.getDirectoryEntries(resolvedPath); } // No need for the more complex logic of getDirectoryEntries; it calls it implicitly. @Override - protected Collection<Dirent> readdir(Path path, boolean followSymlinks) throws IOException { + protected Collection<Dirent> readdir(LocalPath path, boolean followSymlinks) throws IOException { path = followSymlinks ? internalResolveSymlink(path) : path; FileSystem delegate = getDelegate(path); return delegate.readdir(adjustPath(path, delegate), false); } @Override - protected boolean isReadable(Path path) throws IOException { + protected boolean isReadable(LocalPath path) throws IOException { path = internalResolveSymlink(path); FileSystem delegate = getDelegate(path); return delegate.isReadable(adjustPath(path, delegate)); } @Override - protected void setReadable(Path path, boolean readable) throws IOException { + protected void setReadable(LocalPath path, boolean readable) throws IOException { path = internalResolveSymlink(path); checkModifiable(path); FileSystem delegate = getDelegate(path); @@ -369,7 +385,7 @@ } @Override - protected boolean isWritable(Path path) throws IOException { + protected boolean isWritable(LocalPath path) throws IOException { if (!supportsModifications(path)) { return false; } @@ -379,7 +395,7 @@ } @Override - public void setWritable(Path path, boolean writable) throws IOException { + public void setWritable(LocalPath path, boolean writable) throws IOException { checkModifiable(path); path = internalResolveSymlink(path); FileSystem delegate = getDelegate(path); @@ -387,14 +403,14 @@ } @Override - protected boolean isExecutable(Path path) throws IOException { + protected boolean isExecutable(LocalPath path) throws IOException { path = internalResolveSymlink(path); FileSystem delegate = getDelegate(path); return delegate.isExecutable(adjustPath(path, delegate)); } @Override - protected void setExecutable(Path path, boolean executable) throws IOException { + protected void setExecutable(LocalPath path, boolean executable) throws IOException { path = internalResolveSymlink(path); checkModifiable(path); FileSystem delegate = getDelegate(path); @@ -402,28 +418,28 @@ } @Override - protected byte[] getFastDigest(Path path, HashFunction hashFunction) throws IOException { + protected byte[] getFastDigest(LocalPath path, HashFunction hashFunction) throws IOException { path = internalResolveSymlink(path); FileSystem delegate = getDelegate(path); return delegate.getFastDigest(adjustPath(path, delegate), hashFunction); } @Override - public byte[] getxattr(Path path, String name) throws IOException { + public byte[] getxattr(LocalPath path, String name) throws IOException { path = internalResolveSymlink(path); FileSystem delegate = getDelegate(path); return delegate.getxattr(adjustPath(path, delegate), name); } @Override - protected InputStream getInputStream(Path path) throws IOException { + protected InputStream getInputStream(LocalPath path) throws IOException { path = internalResolveSymlink(path); FileSystem delegate = getDelegate(path); return delegate.getInputStream(adjustPath(path, delegate)); } @Override - protected OutputStream getOutputStream(Path path, boolean append) throws IOException { + protected OutputStream getOutputStream(LocalPath path, boolean append) throws IOException { path = internalResolveSymlink(path); checkModifiable(path); FileSystem delegate = getDelegate(path); @@ -431,7 +447,7 @@ } @Override - public void renameTo(Path sourcePath, Path targetPath) throws IOException { + public void renameTo(LocalPath sourcePath, LocalPath targetPath) throws IOException { sourcePath = internalResolveSymlink(sourcePath); FileSystem sourceDelegate = getDelegate(sourcePath); if (!sourceDelegate.supportsModifications(sourcePath)) { @@ -458,13 +474,14 @@ } else { // Copy across filesystems, then delete. // copyFile throws on failure, so delete will never be reached if it fails. - FileSystemUtils.copyFile(sourcePath, targetPath); + FileSystemUtils.copyFile(sourceDelegate, sourcePath, targetDelegate, targetPath); sourceDelegate.delete(sourcePath); } } @Override - protected void createFSDependentHardLink(Path linkPath, Path originalPath) throws IOException { + protected void createFSDependentHardLink(LocalPath linkPath, LocalPath originalPath) + throws IOException { checkModifiable(linkPath); originalPath = internalResolveSymlink(originalPath); @@ -480,9 +497,9 @@ adjustPath(linkPath, linkDelegate), adjustPath(originalPath, originalDelegate)); } - private Path internalResolveSymlink(Path path) throws IOException { + private LocalPath internalResolveSymlink(LocalPath path) throws IOException { while (isSymbolicLink(path)) { - PathFragment pathFragment = resolveOneLink(path); + String pathFragment = resolveOneLink(path); path = path.getRelative(pathFragment); } return path;
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 93c4eb2..333c3bb 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
@@ -17,7 +17,7 @@ import com.google.devtools.build.lib.clock.Clock; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.vfs.FileStatus; -import com.google.devtools.build.lib.vfs.Path; +import com.google.devtools.build.lib.vfs.LocalPath; import java.io.IOException; /** @@ -201,6 +201,5 @@ * @param targetPath where the inode is relocated. * @throws IOException */ - protected void movedTo(Path targetPath) throws IOException { - } + protected void movedTo(LocalPath targetPath) throws IOException {} }
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 ff6d88a..0d35942 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
@@ -21,6 +21,7 @@ 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.LocalPath; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.PathFragment; import java.io.ByteArrayInputStream; @@ -48,7 +49,8 @@ @ThreadSafe public class InMemoryFileSystem extends FileSystem { - private final PathFragment scopeRoot; + private final LocalPath scopeRoot; + private final int scopeSegmentCount; private final Clock clock; // The root inode (a directory). @@ -82,6 +84,7 @@ this.clock = clock; this.rootInode = newRootInode(clock); this.scopeRoot = null; + this.scopeSegmentCount = 0; } /** @@ -89,7 +92,8 @@ * not below scopeRoot is considered to be out of scope. */ public InMemoryFileSystem(Clock clock, PathFragment scopeRoot) { - this.scopeRoot = scopeRoot; + this.scopeRoot = scopeRoot != null ? LocalPath.create(scopeRoot.getPathString()) : null; + this.scopeSegmentCount = scopeRoot != null ? scopeRoot.segmentCount() : 0; this.clock = clock; this.rootInode = newRootInode(clock); } @@ -109,7 +113,7 @@ * @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) { + private boolean inScope(int parentDepth, LocalPath normalizedPath) { if (scopeRoot == null) { return true; } else if (normalizedPath.isAbsolute()) { @@ -120,7 +124,7 @@ // 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(); + return parentDepth - leadingParentReferences(normalizedPath) >= scopeSegmentCount; } } @@ -131,12 +135,14 @@ * <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) { + private int leadingParentReferences(LocalPath normalizedPath) { int leadingParentReferences = 0; - for (int i = 0; - i < normalizedPath.segmentCount() && normalizedPath.getSegment(i).equals(".."); - i++) { - leadingParentReferences++; + for (String segment : normalizedPath.split()) { + if (segment.equals("..")) { + ++leadingParentReferences; + } else { + break; + } } return leadingParentReferences; } @@ -245,11 +251,10 @@ } /** - * Returns a new IOException for the error. The exception message - * contains 'path', and is consistent with the messages returned by - * c.g.common.unix.FilesystemUtils. + * Returns a new IOException for the error. The exception message contains 'path', and is + * consistent with the messages returned by c.g.common.unix.FilesystemUtils. */ - public IOException exception(Path path) throws IOException { + public IOException exception(LocalPath path) throws IOException { String m = path + " (" + message + ")"; if (this == EACCES) { throw new FileAccessExceptionWithError(m, this); @@ -267,48 +272,44 @@ * <p>If <code>/proc/mounts</code> does not exist return {@code "inmemoryfs"}. */ @Override - public String getFileSystemType(Path path) { - return path.getRelative("/proc/mounts").exists() ? super.getFileSystemType(path) : "inmemoryfs"; + public String getFileSystemType(LocalPath path) { + return exists(path.getRelative("/proc/mounts")) ? super.getFileSystemType(path) : "inmemoryfs"; } - /**************************************************************************** - * "Kernel" primitives: basic directory lookup primitives, in topological - * order. + /** + * ************************************************************************** "Kernel" primitives: + * basic directory lookup primitives, in topological order. */ /** - * Unlinks the entry 'child' from its existing parent directory 'dir'. Dual to - * insert. This succeeds even if 'child' names a non-empty directory; we need - * that for renameTo. 'child' must be a member of its parent directory, - * however. Fails if the directory was read-only. + * Unlinks the entry 'child' from its existing parent directory 'dir'. Dual to insert. This + * succeeds even if 'child' names a non-empty directory; we need that for renameTo. 'child' must + * be a member of its parent directory, however. Fails if the directory was read-only. */ - private void unlink(InMemoryDirectoryInfo dir, String child, Path errorPath) + private void unlink(InMemoryDirectoryInfo dir, String child, LocalPath errorPath) throws IOException { if (!dir.isWritable()) { throw Error.EACCES.exception(errorPath); } dir.removeChild(child); } /** - * Inserts inode 'childInode' into the existing directory 'dir' under the - * specified 'name'. Dual to unlink. Fails if the directory was read-only. + * Inserts inode 'childInode' into the existing directory 'dir' under the specified 'name'. Dual + * to unlink. Fails if the directory was read-only. */ - private void insert(InMemoryDirectoryInfo dir, String child, - InMemoryContentInfo childInode, Path errorPath) + private void insert( + InMemoryDirectoryInfo dir, String child, InMemoryContentInfo childInode, LocalPath errorPath) throws IOException { if (!dir.isWritable()) { throw Error.EACCES.exception(errorPath); } dir.addChild(child, childInode); } /** - * Given an existing directory 'dir', looks up 'name' within it and returns - * its inode. Assumes the file exists, unless 'create', in which case it will - * try to create it. May fail with ENOTDIR, EACCES, ENOENT. Error messages - * will be reported against file 'path'. + * Given an existing directory 'dir', looks up 'name' within it and returns its inode. Assumes the + * file exists, unless 'create', in which case it will try to create it. May fail with ENOTDIR, + * EACCES, ENOENT. Error messages will be reported against file 'path'. */ - private InMemoryContentInfo directoryLookup(InMemoryContentInfo dir, - String name, - boolean create, - Path path) throws IOException { + private InMemoryContentInfo directoryLookup( + InMemoryContentInfo dir, String name, boolean create, LocalPath path) throws IOException { if (!dir.isDirectory()) { throw Error.ENOTDIR.exception(path); } InMemoryDirectoryInfo imdi = (InMemoryDirectoryInfo) dir; if (!imdi.isExecutable()) { throw Error.EACCES.exception(path); } @@ -336,7 +337,8 @@ * * <p>May fail with ENOTDIR, ENOENT, EACCES, ELOOP. */ - private synchronized InMemoryContentInfo pathWalk(Path path, boolean create) throws IOException { + private synchronized InMemoryContentInfo pathWalk(LocalPath 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 @@ -348,8 +350,7 @@ // and it may only appear as part of a contiguous prefix sequence. Stack<String> stack = new Stack<>(); - PathFragment rootPathFragment = getRootDirectory().asFragment(); - for (Path p = path; !p.asFragment().equals(rootPathFragment); p = p.getParentDirectory()) { + for (LocalPath p = path; p != null && !p.isRoot(); p = p.getParentDirectory()) { stack.push(p.getBaseName()); } @@ -366,7 +367,7 @@ // ENOENT on last segment with 'create' => create a new file. InMemoryContentInfo child = directoryLookup(inode, name, create && stack.isEmpty(), path); if (child.isSymbolicLink()) { - PathFragment linkTarget = ((InMemoryLinkInfo) child).getNormalizedLinkContent(); + LocalPath linkTarget = ((InMemoryLinkInfo) child).getNormalizedLinkContent(); if (!inScope(parentDepth, linkTarget)) { throw Error.ENOENT.exception(path); } @@ -377,8 +378,9 @@ if (traversals > MAX_TRAVERSALS) { throw Error.ELOOP.exception(path); } - for (int ii = linkTarget.segmentCount() - 1; ii >= 0; --ii) { - stack.push(linkTarget.getSegment(ii)); // Note this may include ".." segments. + List<String> linkSegments = linkTarget.split(); + for (int ii = linkSegments.size() - 1; ii >= 0; --ii) { + stack.push(linkSegments.get(ii)); // Note this may include ".." segments. } } else { inode = child; @@ -388,12 +390,11 @@ } /** - * Given 'path', returns the existing directory inode it designates, - * following symbolic links. + * Given 'path', returns the existing directory inode it designates, following symbolic links. * * <p>May fail with ENOTDIR, or any exception from pathWalk. */ - private InMemoryDirectoryInfo getDirectory(Path path) throws IOException { + private InMemoryDirectoryInfo getDirectory(LocalPath path) throws IOException { InMemoryContentInfo dirInfo = pathWalk(path, false); if (!dirInfo.isDirectory()) { throw Error.ENOTDIR.exception(path); @@ -403,28 +404,28 @@ } /** - * Helper method for stat, scopeLimitedStat: lock the internal state and return the - * path's (no symlink-followed) stat if the path's parent directory is within scope, - * else return an "out of scope" reference to the path's parent directory (which will - * presumably be re-delegated to another FS). + * Helper method for stat, scopeLimitedStat: lock the internal state and return the path's (no + * symlink-followed) stat if the path's parent directory is within scope, else return an "out of + * scope" reference to the path's parent directory (which will presumably be re-delegated to + * another FS). */ - private synchronized InMemoryContentInfo getNoFollowStatOrOutOfScopeParent(Path path) - throws IOException { + private synchronized InMemoryContentInfo getNoFollowStatOrOutOfScopeParent(LocalPath path) + throws IOException { InMemoryDirectoryInfo dirInfo = getDirectory(path.getParentDirectory()); return directoryLookup(dirInfo, path.getBaseName(), /*create=*/ false, path); } /** - * Given 'path', returns the existing inode it designates, optionally - * following symbolic links. Analogous to UNIX stat(2)/lstat(2), except that - * it returns a mutable inode we can modify directly. + * Given 'path', returns the existing inode it designates, optionally following symbolic links. + * Analogous to UNIX stat(2)/lstat(2), except that it returns a mutable inode we can modify + * directly. */ @Override - public FileStatus stat(Path path, boolean followSymlinks) throws IOException { + public FileStatus stat(LocalPath path, boolean followSymlinks) throws IOException { if (followSymlinks) { return scopeLimitedStat(path, true); } else { - if (path.equals(getRootDirectory())) { + if (path.isRoot()) { return rootInode; } else { return getNoFollowStatOrOutOfScopeParent(path); @@ -434,7 +435,7 @@ @Override @Nullable - public FileStatus statIfFound(Path path, boolean followSymlinks) throws IOException { + public FileStatus statIfFound(LocalPath path, boolean followSymlinks) throws IOException { try { return stat(path, followSymlinks); } catch (IOException e) { @@ -452,12 +453,12 @@ * Version of stat that returns an inode if the input path stays entirely within this file * system's scope, otherwise throws. */ - private InMemoryContentInfo scopeLimitedStat(Path path, boolean followSymlinks) + private InMemoryContentInfo scopeLimitedStat(LocalPath path, boolean followSymlinks) throws IOException { if (followSymlinks) { return pathWalk(path, false); } else { - if (path.equals(getRootDirectory())) { + if (path.isRoot()) { return rootInode; } else { return getNoFollowStatOrOutOfScopeParent(path); @@ -465,21 +466,20 @@ } } - /**************************************************************************** - * FileSystem methods + /** + * ************************************************************************** FileSystem methods */ /** - * This is a helper routing for {@link #resolveSymbolicLinks(Path)}, i.e. - * the "user-mode" routing for canonicalising paths. It is analogous to the - * code in glibc's realpath(3). + * This is a helper routing for {@link #resolveSymbolicLinks(LocalPath)}, i.e. the "user-mode" + * routing for canonicalising paths. It is analogous to the code in glibc's realpath(3). * - * <p>Just like realpath, resolveSymbolicLinks requires a quadratic number of - * directory lookups: n path segments are statted, and each stat requires a - * linear amount of work in the "kernel" routine. + * <p>Just like realpath, resolveSymbolicLinks requires a quadratic number of directory lookups: n + * path segments are statted, and each stat requires a linear amount of work in the "kernel" + * routine. */ @Override - protected PathFragment resolveOneLink(Path path) throws IOException { + protected String resolveOneLink(LocalPath path) throws IOException { // Beware, this seemingly simple code belies the complex specification of // FileSystem.resolveOneLink(). InMemoryContentInfo status = scopeLimitedStat(path, false); @@ -487,7 +487,7 @@ } @Override - protected boolean isDirectory(Path path, boolean followSymlinks) { + protected boolean isDirectory(LocalPath path, boolean followSymlinks) { try { return stat(path, followSymlinks).isDirectory(); } catch (IOException e) { @@ -496,7 +496,7 @@ } @Override - protected boolean isFile(Path path, boolean followSymlinks) { + protected boolean isFile(LocalPath path, boolean followSymlinks) { try { return stat(path, followSymlinks).isFile(); } catch (IOException e) { @@ -505,7 +505,7 @@ } @Override - protected boolean isSpecialFile(Path path, boolean followSymlinks) { + protected boolean isSpecialFile(LocalPath path, boolean followSymlinks) { try { return stat(path, followSymlinks).isSpecialFile(); } catch (IOException e) { @@ -514,7 +514,7 @@ } @Override - protected boolean isSymbolicLink(Path path) { + protected boolean isSymbolicLink(LocalPath path) { try { return stat(path, false).isSymbolicLink(); } catch (IOException e) { @@ -523,7 +523,7 @@ } @Override - protected boolean exists(Path path, boolean followSymlinks) { + protected boolean exists(LocalPath path, boolean followSymlinks) { try { stat(path, followSymlinks); return true; @@ -533,13 +533,13 @@ } @Override - protected boolean isReadable(Path path) throws IOException { + protected boolean isReadable(LocalPath path) throws IOException { InMemoryContentInfo status = scopeLimitedStat(path, true); return status.isReadable(); } @Override - protected void setReadable(Path path, boolean readable) throws IOException { + protected void setReadable(LocalPath path, boolean readable) throws IOException { synchronized (this) { InMemoryContentInfo status = scopeLimitedStat(path, true); status.setReadable(readable); @@ -547,13 +547,13 @@ } @Override - protected boolean isWritable(Path path) throws IOException { + protected boolean isWritable(LocalPath path) throws IOException { InMemoryContentInfo status = scopeLimitedStat(path, true); return status.isWritable(); } @Override - public void setWritable(Path path, boolean writable) throws IOException { + public void setWritable(LocalPath path, boolean writable) throws IOException { InMemoryContentInfo status; synchronized (this) { status = scopeLimitedStat(path, true); @@ -562,14 +562,13 @@ } @Override - protected boolean isExecutable(Path path) throws IOException { + protected boolean isExecutable(LocalPath path) throws IOException { InMemoryContentInfo status = scopeLimitedStat(path, true); return status.isExecutable(); } @Override - protected void setExecutable(Path path, boolean executable) - throws IOException { + protected void setExecutable(LocalPath path, boolean executable) throws IOException { synchronized (this) { InMemoryContentInfo status = scopeLimitedStat(path, true); status.setExecutable(executable); @@ -577,17 +576,17 @@ } @Override - public boolean supportsModifications(Path path) { + public boolean supportsModifications(LocalPath path) { return true; } @Override - public boolean supportsSymbolicLinksNatively(Path path) { + public boolean supportsSymbolicLinksNatively(LocalPath path) { return true; } @Override - public boolean supportsHardLinksNatively(Path path) { + public boolean supportsHardLinksNatively(LocalPath path) { return true; } @@ -597,8 +596,8 @@ } @Override - public boolean createDirectory(Path path) throws IOException { - if (path.equals(getRootDirectory())) { + public boolean createDirectory(LocalPath path) throws IOException { + if (path.isRoot()) { throw Error.EACCES.exception(path); } @@ -624,9 +623,8 @@ } @Override - protected void createSymbolicLink(Path path, PathFragment targetFragment) - throws IOException { - if (path.equals(getRootDirectory())) { + protected void createSymbolicLink(LocalPath path, String targetFragment) throws IOException { + if (path.isRoot()) { throw Error.EACCES.exception(path); } @@ -640,7 +638,7 @@ } @Override - protected PathFragment readSymbolicLink(Path path) throws IOException { + protected String readSymbolicLink(LocalPath path) throws IOException { InMemoryContentInfo status = scopeLimitedStat(path, false); if (status.isSymbolicLink()) { Preconditions.checkState(status instanceof InMemoryLinkInfo); @@ -651,13 +649,12 @@ } @Override - protected long getFileSize(Path path, boolean followSymlinks) - throws IOException { + protected long getFileSize(LocalPath path, boolean followSymlinks) throws IOException { return stat(path, followSymlinks).getSize(); } @Override - protected Collection<String> getDirectoryEntries(Path path) throws IOException { + protected Collection<String> getDirectoryEntries(LocalPath path) throws IOException { synchronized (this) { InMemoryDirectoryInfo dirInfo = getDirectory(path); FileStatus status = stat(path, false); @@ -678,8 +675,8 @@ } @Override - public boolean delete(Path path) throws IOException { - if (path.equals(getRootDirectory())) { + public boolean delete(LocalPath path) throws IOException { + if (path.isRoot()) { throw Error.EBUSY.exception(path); } if (!exists(path, false)) { return false; } @@ -696,13 +693,12 @@ } @Override - protected long getLastModifiedTime(Path path, boolean followSymlinks) - throws IOException { + protected long getLastModifiedTime(LocalPath path, boolean followSymlinks) throws IOException { return stat(path, followSymlinks).getLastModifiedTime(); } @Override - public void setLastModifiedTime(Path path, long newTime) throws IOException { + public void setLastModifiedTime(LocalPath path, long newTime) throws IOException { synchronized (this) { InMemoryContentInfo status = scopeLimitedStat(path, true); status.setLastModifiedTime(newTime == -1L ? clock.currentTimeMillis() : newTime); @@ -710,13 +706,13 @@ } @Override - protected InputStream getInputStream(Path path) throws IOException { + protected InputStream getInputStream(LocalPath path) throws IOException { synchronized (this) { InMemoryContentInfo status = scopeLimitedStat(path, true); if (status.isDirectory()) { throw Error.EISDIR.exception(path); } - if (!path.isReadable()) { + if (!isReadable(path)) { throw Error.EACCES.exception(path); } Preconditions.checkState(status instanceof FileInfo); @@ -725,7 +721,7 @@ } /** Creates a new file at the given path and returns its inode. */ - private InMemoryContentInfo getOrCreateWritableInode(Path path) throws IOException { + private InMemoryContentInfo getOrCreateWritableInode(LocalPath 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 @@ -742,8 +738,7 @@ } @Override - protected OutputStream getOutputStream(Path path, boolean append) - throws IOException { + protected OutputStream getOutputStream(LocalPath path, boolean append) throws IOException { synchronized (this) { InMemoryContentInfo status = getOrCreateWritableInode(path); return ((FileInfo) status).getOutputStream(append); @@ -751,12 +746,11 @@ } @Override - public void renameTo(Path sourcePath, Path targetPath) - throws IOException { - if (sourcePath.equals(getRootDirectory())) { + public void renameTo(LocalPath sourcePath, LocalPath targetPath) throws IOException { + if (sourcePath.isRoot()) { throw Error.EACCES.exception(sourcePath); } - if (targetPath.equals(getRootDirectory())) { + if (targetPath.isRoot()) { throw Error.EACCES.exception(targetPath); } synchronized (this) { @@ -800,11 +794,11 @@ } @Override - protected void createFSDependentHardLink(Path linkPath, Path originalPath) + protected void createFSDependentHardLink(LocalPath linkPath, LocalPath originalPath) throws IOException { // Same check used when creating a symbolic link - if (originalPath.equals(getRootDirectory())) { + if (originalPath.isRoot()) { throw Error.EACCES.exception(originalPath); }
diff --git a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java index 107f319..cee3ebd 100644 --- a/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java +++ b/src/main/java/com/google/devtools/build/lib/vfs/inmemoryfs/InMemoryLinkInfo.java
@@ -16,7 +16,7 @@ import com.google.devtools.build.lib.clock.Clock; import com.google.devtools.build.lib.concurrent.ThreadSafety.Immutable; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; -import com.google.devtools.build.lib.vfs.PathFragment; +import com.google.devtools.build.lib.vfs.LocalPath; /** * This interface represents a symbolic link to an absolute or relative path, @@ -25,13 +25,13 @@ @ThreadSafe @Immutable class InMemoryLinkInfo extends InMemoryContentInfo { - private final PathFragment linkContent; - private final PathFragment normalizedLinkContent; + private final String linkContent; + private final LocalPath normalizedLinkContent; - InMemoryLinkInfo(Clock clock, PathFragment linkContent) { + InMemoryLinkInfo(Clock clock, String linkContent) { super(clock); this.linkContent = linkContent; - this.normalizedLinkContent = linkContent.normalize(); + this.normalizedLinkContent = LocalPath.create(linkContent); } @Override @@ -59,18 +59,16 @@ return linkContent.toString().length(); } - /** - * Returns the content of the symbolic link. - */ - PathFragment getLinkContent() { + /** Returns the content of the symbolic link. */ + String getLinkContent() { return linkContent; } /** - * Returns the content of the symbolic link, with ".." and "." removed - * (except for the possibility of necessary ".." segments at the beginning). + * Returns the content of the symbolic link, with ".." and "." removed (except for the possibility + * of necessary ".." segments at the beginning). */ - PathFragment getNormalizedLinkContent() { + LocalPath getNormalizedLinkContent() { return normalizedLinkContent; }
diff --git a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java index af97e27..c98a6c1 100644 --- a/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java +++ b/src/main/java/com/google/devtools/build/lib/windows/WindowsFileSystem.java
@@ -21,6 +21,7 @@ import com.google.devtools.build.lib.vfs.FileStatus; import com.google.devtools.build.lib.vfs.FileSystem; import com.google.devtools.build.lib.vfs.JavaIoFileSystem; +import com.google.devtools.build.lib.vfs.LocalPath; import com.google.devtools.build.lib.vfs.Path; import com.google.devtools.build.lib.vfs.Path.PathFactory; import com.google.devtools.build.lib.vfs.PathFragment; @@ -32,41 +33,11 @@ import java.nio.file.LinkOption; import java.nio.file.attribute.DosFileAttributes; import java.util.Arrays; -import java.util.regex.Matcher; -import java.util.regex.Pattern; -import javax.annotation.Nullable; /** File system implementation for Windows. */ @ThreadSafe public class WindowsFileSystem extends JavaIoFileSystem { - // Properties of 8dot3 (DOS-style) short file names: - // - they are at most 11 characters long - // - they have a prefix (before "~") that is {1..6} characters long, may contain numbers, letters, - // "_", even "~", and maybe even more - // - they have a "~" after the prefix - // - have {1..6} numbers after "~" (according to [1] this is only one digit, but MSDN doesn't - // clarify this), the combined length up till this point is at most 8 - // - they have an optional "." afterwards, and another {0..3} more characters - // - just because a path looks like a short name it isn't necessarily one; the user may create - // such names and they'd resolve to themselves - // [1] https://en.wikipedia.org/wiki/8.3_filename#VFAT_and_Computer-generated_8.3_filenames - // bullet point (3) (on 2016-12-05) - @VisibleForTesting - static final Predicate<String> SHORT_NAME_MATCHER = - new Predicate<String>() { - private final Pattern pattern = Pattern.compile("^(.{1,6})~([0-9]{1,6})(\\..{0,3}){0,1}"); - - @Override - public boolean apply(@Nullable String input) { - Matcher m = pattern.matcher(input); - return input.length() <= 12 - && m.matches() - && m.groupCount() >= 2 - && (m.group(1).length() + m.group(2).length()) < 8; // the "~" makes it at most 8 - } - }; - /** Resolves DOS-style, shortened path names, returning the last segment's long form. */ private static final Function<String, String> WINDOWS_SHORT_PATH_RESOLVER = path -> { @@ -114,7 +85,7 @@ } String resolvedChild = child; - if (parent != null && !parent.isRootDirectory() && SHORT_NAME_MATCHER.apply(child)) { + if (parent != null && !parent.isRootDirectory() && WindowsShortPath.isShortPath(child)) { String pathString = parent.getPathString(); if (!pathString.endsWith("/")) { pathString += "/"; @@ -286,18 +257,18 @@ } @Override - public String getFileSystemType(Path path) { + public String getFileSystemType(LocalPath path) { // TODO(laszlocsomor): implement this properly, i.e. actually query this information from // somewhere (java.nio.Filesystem? System.getProperty? implement JNI method and use WinAPI?). return "ntfs"; } @Override - protected void createSymbolicLink(Path linkPath, PathFragment targetFragment) throws IOException { - Path targetPath = - targetFragment.isAbsolute() - ? getPath(targetFragment) - : linkPath.getParentDirectory().getRelative(targetFragment); + protected void createSymbolicLink(LocalPath linkPath, String targetFragment) throws IOException { + LocalPath targetPath = LocalPath.create(targetFragment); + if (!targetPath.isAbsolute()) { + targetPath = linkPath.getParentDirectory().getRelative(targetPath); + } try { java.nio.file.Path link = getIoFile(linkPath).toPath(); java.nio.file.Path target = getIoFile(targetPath).toPath(); @@ -317,7 +288,7 @@ } @Override - public boolean supportsSymbolicLinksNatively(Path path) { + public boolean supportsSymbolicLinksNatively(LocalPath path) { return false; } @@ -343,7 +314,7 @@ } @Override - protected FileStatus stat(Path path, boolean followSymlinks) throws IOException { + protected FileStatus stat(LocalPath path, boolean followSymlinks) throws IOException { File file = getIoFile(path); final DosFileAttributes attributes; try { @@ -402,7 +373,7 @@ } @Override - protected boolean isDirectory(Path path, boolean followSymlinks) { + protected boolean isDirectory(LocalPath path, boolean followSymlinks) { if (!followSymlinks) { try { if (isJunction(getIoFile(path))) {