// 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.common.hash.Hasher; import com.google.devtools.build.lib.concurrent.ThreadSafety.ThreadSafe; import com.google.devtools.build.lib.skyframe.serialization.autocodec.AutoCodec; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrintable; import com.google.devtools.build.lib.skylarkinterface.SkylarkPrinter; import com.google.devtools.build.lib.util.FileType; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.OutputStream; import java.io.Serializable; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import javax.annotation.Nullable; /** * A local file path representing a file on the host machine. You should use this when you want to * access local files via the file system. * *

Paths are always absolute. * *

Strings are normalized with '.' and '..' removed and resolved (if possible), any multiple * slashes ('/') removed, and any trailing slash also removed. Windows drive letters are uppercased. * The current implementation does not touch the incoming path string unless the string actually * needs to be normalized. * *

There is some limited support for Windows-style paths. Most importantly, drive identifiers in * front of a path (c:/abc) are supported and such paths are correctly recognized as absolute, as * are paths with backslash separators (C:\\foo\\bar). However, advanced Windows-style features like * \\\\network\\paths and \\\\?\\unc\\paths are not supported. We are currently using forward * slashes ('/') even on Windows, so backslashes '\' get converted to forward slashes during * normalization. * *

Mac and Windows file paths are case insensitive. Case is preserved. */ @ThreadSafe @AutoCodec public class Path implements Comparable, Serializable, SkylarkPrintable, FileType.HasFileType { private static FileSystem fileSystemForSerialization; /** * We need to specify used FileSystem. In this case we can save memory during the serialization. */ public static void setFileSystemForSerialization(FileSystem fileSystem) { fileSystemForSerialization = fileSystem; } /** * Returns FileSystem that we are using. */ public static FileSystem getFileSystemForSerialization() { return fileSystemForSerialization; } private static final OsPathPolicy OS = OsPathPolicy.getFilePathOs(); private static final char SEPARATOR = OS.getSeparator(); private String path; private int driveStrLength; // 1 on Unix, 3 on Windows private FileSystem fileSystem; /** Creates a local path that is specific to the host OS. */ static Path create(String path, FileSystem fileSystem) { Preconditions.checkNotNull(path); int normalizationLevel = OS.needsToNormalize(path); String normalizedPath = OS.normalize(path, normalizationLevel); return createAlreadyNormalized(normalizedPath, fileSystem); } @AutoCodec.VisibleForSerialization @AutoCodec.Instantiator static Path createAlreadyNormalized(String path, FileSystem fileSystem) { int driveStrLength = OS.getDriveStrLength(path); return createAlreadyNormalized(path, driveStrLength, fileSystem); } static Path createAlreadyNormalized(String path, int driveStrLength, FileSystem fileSystem) { return new Path(path, driveStrLength, fileSystem); } /** This method expects path to already be normalized. */ private Path(String path, int driveStrLength, FileSystem fileSystem) { Preconditions.checkArgument(driveStrLength > 0, "Paths must be absolute: '%s'", path); this.path = Preconditions.checkNotNull(path); this.driveStrLength = driveStrLength; this.fileSystem = fileSystem; } public String getPathString() { return path; } @Override public String filePathForFileTypeMatcher() { return path; } /** * Returns the name of the leaf file or directory. * *

If called on a {@link Path} instance for a mount name (eg. '/' or 'C:/'), the empty string * is returned. */ public String getBaseName() { int lastSeparator = path.lastIndexOf(SEPARATOR); return lastSeparator < driveStrLength ? path.substring(driveStrLength) : path.substring(lastSeparator + 1); } /** Synonymous with {@link Path#getRelative(String)}. */ public Path getChild(String child) { FileSystemUtils.checkBaseName(child); return getRelative(child); } /** * Returns a {@link Path} instance representing the relative path between this {@link Path} and * the given path. */ public Path getRelative(PathFragment other) { Preconditions.checkNotNull(other); String otherStr = other.getPathString(); // Fast-path: The path fragment is already normal, use cheaper normalization check return getRelative(otherStr, other.getDriveStrLength(), OS.needsToNormalizeSuffix(otherStr)); } /** * Returns a {@link Path} instance representing the relative path between this {@link Path} and * the given path. */ public Path getRelative(String other) { Preconditions.checkNotNull(other); return getRelative(other, OS.getDriveStrLength(other), OS.needsToNormalize(other)); } private Path getRelative(String other, int otherDriveStrLength, int normalizationLevel) { if (other.isEmpty()) { return this; } // This is an absolute path, simply return it if (otherDriveStrLength > 0) { String normalizedPath = OS.normalize(other, normalizationLevel); return new Path(normalizedPath, otherDriveStrLength, fileSystem); } String newPath; if (path.length() == driveStrLength) { newPath = path + other; } else { newPath = path + '/' + other; } // Note that even if other came from a PathFragment instance we still might // need to normalize the result if (for instance) other is a path that // starts with '..' newPath = OS.normalize(newPath, normalizationLevel); return new Path(newPath, driveStrLength, fileSystem); } /** * Returns the parent directory of this {@link Path}. * *

If called on a root (like '/'), it returns null. */ @Nullable public Path getParentDirectory() { int lastSeparator = path.lastIndexOf(SEPARATOR); if (lastSeparator < driveStrLength) { if (path.length() > driveStrLength) { String newPath = path.substring(0, driveStrLength); return new Path(newPath, driveStrLength, fileSystem); } else { return null; } } String newPath = path.substring(0, lastSeparator); return new Path(newPath, driveStrLength, fileSystem); } /** * Returns the drive. * *

On unix, this will return "/". On Windows it will return the drive letter, like "C:/". */ public String getDriveStr() { return path.substring(0, driveStrLength); } /** * Returns the {@link Path} relative to the base {@link Path}. * *

For example, Path.create("foo/bar/wiz").relativeTo(Path.create("foo")) * returns Path.create("bar/wiz"). * *

If the {@link Path} is not a child of the passed {@link Path} an {@link * IllegalArgumentException} is thrown. In particular, this will happen whenever the two {@link * Path} instances aren't both absolute or both relative. */ public PathFragment relativeTo(Path base) { Preconditions.checkNotNull(base); checkSameFileSystem(base); String basePath = base.path; if (!OS.startsWith(path, basePath)) { throw new IllegalArgumentException( String.format("Path '%s' is not under '%s', cannot relativize", this, base)); } int bn = basePath.length(); if (bn == 0) { return PathFragment.createAlreadyNormalized(path, driveStrLength); } if (path.length() == bn) { return PathFragment.EMPTY_FRAGMENT; } final int lastSlashIndex; if (basePath.charAt(bn - 1) == '/') { lastSlashIndex = bn - 1; } else { lastSlashIndex = bn; } if (path.charAt(lastSlashIndex) != '/') { throw new IllegalArgumentException( String.format("Path '%s' is not under '%s', cannot relativize", this, base)); } String newPath = path.substring(lastSlashIndex + 1); return PathFragment.createAlreadyNormalized(newPath, 0); } /** * Returns whether this path is an ancestor of another path. * *

A path is considered an ancestor of itself. */ public boolean startsWith(Path other) { if (fileSystem != other.fileSystem) { return false; } return startsWith(other.path, other.driveStrLength); } /** * Returns whether this path is an ancestor of another path. * *

A path is considered an ancestor of itself. * *

An absolute path can never be an ancestor of a relative path fragment. */ public boolean startsWith(PathFragment other) { if (!other.isAbsolute()) { return false; } String otherPath = other.getPathString(); return startsWith(otherPath, OS.getDriveStrLength(otherPath)); } private boolean startsWith(String otherPath, int otherDriveStrLength) { Preconditions.checkNotNull(otherPath); if (otherPath.length() > path.length()) { return false; } if (driveStrLength != otherDriveStrLength) { return false; } if (!OS.startsWith(path, otherPath)) { return false; } return path.length() == otherPath.length() // Handle equal paths || otherPath.length() == driveStrLength // Handle (eg.) 'C:/foo' starts with 'C:/' // Handle 'true' ancestors, eg. "foo/bar" starts with "foo", but does not start with "fo" || path.charAt(otherPath.length()) == SEPARATOR; } public FileSystem getFileSystem() { return fileSystem; } public PathFragment asFragment() { return PathFragment.createAlreadyNormalized(path, driveStrLength); } @Override public String toString() { return path; } @Override public boolean equals(Object o) { if (this == o) { return true; } if (o == null || getClass() != o.getClass()) { return false; } Path other = (Path) o; if (fileSystem != other.fileSystem) { return false; } return OS.equals(this.path, other.path); } @Override public int hashCode() { // Do not include file system for efficiency. // In practice we never construct paths from different file systems. return OS.hash(this.path); } @Override public int compareTo(Path o) { // If they are on different file systems, the file system decides the ordering. FileSystem otherFs = o.getFileSystem(); if (!fileSystem.equals(otherFs)) { int thisFileSystemHash = System.identityHashCode(fileSystem); int otherFileSystemHash = System.identityHashCode(otherFs); if (thisFileSystemHash < otherFileSystemHash) { return -1; } else if (thisFileSystemHash > otherFileSystemHash) { return 1; } } return OS.compare(this.path, o.path); } @Override public void repr(SkylarkPrinter printer) { printer.append(path); } @Override public void str(SkylarkPrinter printer) { repr(printer); } /** Returns true iff this path denotes an existing file of any kind. Follows symbolic links. */ public boolean exists() { return fileSystem.exists(this, true); } /** * Returns true iff this path denotes an existing file of any kind. * * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the * link is dereferenced until a file other than a symbolic link is found */ public boolean exists(Symlinks followSymlinks) { return fileSystem.exists(this, followSymlinks.toBoolean()); } /** * Returns a new, immutable collection containing the names of all entities within the directory * denoted by the current path. Follows symbolic links. * * @throws FileNotFoundException If the directory is not found * @throws IOException If the path does not denote a directory */ public Collection getDirectoryEntries() throws IOException, FileNotFoundException { Collection entries = fileSystem.getDirectoryEntries(this); Collection result = new ArrayList<>(entries.size()); for (String entry : entries) { result.add(getChild(entry)); } return result; } /** * Returns a collection of the names and types of all entries within the directory denoted by the * current path. Follows symbolic links if {@code followSymlinks} is true. Note that the order of * the returned entries is not guaranteed. * * @param followSymlinks whether to follow symlinks or not * @throws FileNotFoundException If the directory is not found * @throws IOException If the path does not denote a directory */ public Collection readdir(Symlinks followSymlinks) throws IOException { return fileSystem.readdir(this, followSymlinks.toBoolean()); } /** * Returns the status of a file, following symbolic links. * * @throws IOException if there was an error obtaining the file status. Note, some implementations * may defer the I/O, and hence the throwing of the exception, until the accessor methods of * {@code FileStatus} are called. */ public FileStatus stat() throws IOException { return fileSystem.stat(this, true); } /** Like stat(), but returns null on file-nonexistence instead of throwing. */ public FileStatus statNullable() { return statNullable(Symlinks.FOLLOW); } /** Like stat(), but returns null on file-nonexistence instead of throwing. */ public FileStatus statNullable(Symlinks symlinks) { return fileSystem.statNullable(this, symlinks.toBoolean()); } /** * Returns the status of a file, optionally following symbolic links. * * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the * link is dereferenced until a file other than a symbolic link is found * @throws IOException if there was an error obtaining the file status. Note, some implementations * may defer the I/O, and hence the throwing of the exception, until the accessor methods of * {@code FileStatus} are called */ public FileStatus stat(Symlinks followSymlinks) throws IOException { return fileSystem.stat(this, followSymlinks.toBoolean()); } /** * Like {@link #stat}, but may return null if the file is not found (corresponding to {@code * ENOENT} and {@code ENOTDIR} in Unix's stat(2) function) instead of throwing. Follows symbolic * links. */ public FileStatus statIfFound() throws IOException { return fileSystem.statIfFound(this, true); } /** * Like {@link #stat}, but may return null if the file is not found (corresponding to {@code * ENOENT} and {@code ENOTDIR} in Unix's stat(2) function) instead of throwing. * * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the * link is dereferenced until a file other than a symbolic link is found */ public FileStatus statIfFound(Symlinks followSymlinks) throws IOException { return fileSystem.statIfFound(this, followSymlinks.toBoolean()); } /** Returns true iff this path denotes an existing directory. Follows symbolic links. */ public boolean isDirectory() { return fileSystem.isDirectory(this, true); } /** * Returns true iff this path denotes an existing directory. * * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the * link is dereferenced until a file other than a symbolic link is found */ public boolean isDirectory(Symlinks followSymlinks) { return fileSystem.isDirectory(this, followSymlinks.toBoolean()); } /** * Returns true iff this path denotes an existing regular or special file. Follows symbolic links. * *

For our purposes, "file" includes special files (socket, fifo, block or char devices) too; * it excludes symbolic links and directories. */ public boolean isFile() { return fileSystem.isFile(this, true); } /** * Returns true iff this path denotes an existing regular or special file. * *

For our purposes, a "file" includes special files (socket, fifo, block or char devices) too; * it excludes symbolic links and directories. * * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the * link is dereferenced until a file other than a symbolic link is found. */ public boolean isFile(Symlinks followSymlinks) { return fileSystem.isFile(this, followSymlinks.toBoolean()); } /** * Returns true iff this path denotes an existing special file (e.g. fifo). Follows symbolic * links. */ public boolean isSpecialFile() { return fileSystem.isSpecialFile(this, true); } /** * Returns true iff this path denotes an existing special file (e.g. fifo). * * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the * link is dereferenced until a path other than a symbolic link is found. */ public boolean isSpecialFile(Symlinks followSymlinks) { return fileSystem.isSpecialFile(this, followSymlinks.toBoolean()); } /** * Returns true iff this path denotes an existing symbolic link. Does not follow symbolic links. */ public boolean isSymbolicLink() { return fileSystem.isSymbolicLink(this); } /** * Returns an output stream to the file denoted by the current path, creating it and truncating it * if necessary. The stream is opened for writing. * * @throws FileNotFoundException If the file cannot be found or created. * @throws IOException If a different error occurs. */ public OutputStream getOutputStream() throws IOException, FileNotFoundException { return getOutputStream(false); } /** * Returns an output stream to the file denoted by the current path, creating it and truncating it * if necessary. The stream is opened for writing. * * @param append whether to open the file in append mode. * @throws FileNotFoundException If the file cannot be found or created. * @throws IOException If a different error occurs. */ public OutputStream getOutputStream(boolean append) throws IOException, FileNotFoundException { return fileSystem.getOutputStream(this, append); } /** * Creates a directory with the name of the current path, not following symbolic links. Returns * normally iff the directory exists after the call: true if the directory was created by this * call, false if the directory was already in existence. Throws an exception if the directory * could not be created for any reason. * * @throws IOException if the directory creation failed for any reason */ public boolean createDirectory() throws IOException { return fileSystem.createDirectory(this); } /** * Ensures that the directory with the name of the current path and all its ancestor directories * exist. * *

Does not return whether the directory already existed or was created by some other * concurrent call to this method. * * @throws IOException if the directory creation failed for any reason */ public void createDirectoryAndParents() throws IOException { fileSystem.createDirectoryAndParents(this); } /** * Creates a symbolic link with the name of the current path, following symbolic links. The * referent of the created symlink is is the absolute path "target"; it is not possible to create * relative symbolic links via this method. * * @throws IOException if the creation of the symbolic link was unsuccessful for any reason */ public void createSymbolicLink(Path target) throws IOException { checkSameFileSystem(target); fileSystem.createSymbolicLink(this, target.asFragment()); } /** * Creates a symbolic link with the name of the current path, following symbolic links. The * referent of the created symlink is is the path fragment "target", which may be absolute or * relative. * * @throws IOException if the creation of the symbolic link was unsuccessful for any reason */ public void createSymbolicLink(PathFragment target) throws IOException { fileSystem.createSymbolicLink(this, target); } /** * Returns the target of the current path, which must be a symbolic link. The link contents are * returned exactly, and may contain an absolute or relative path. Analogous to readlink(2). * *

Note: for {@link FileSystem}s where {@link FileSystem#supportsSymbolicLinksNatively(Path)} * returns false, this method will throw an {@link UnsupportedOperationException} if the link * points to a non-existent file. * * @return the content (i.e. target) of the symbolic link * @throws IOException if the current path is not a symbolic link, or the contents of the link * could not be read for any reason */ public PathFragment readSymbolicLink() throws IOException { return fileSystem.readSymbolicLink(this); } /** * If the current path is a symbolic link, returns the target of this symbolic link. The semantics * are intentionally left underspecified otherwise to permit efficient implementations. * * @return the content (i.e. target) of the symbolic link * @throws IOException if the current path is not a symbolic link, or the contents of the link * could not be read for any reason */ public PathFragment readSymbolicLinkUnchecked() throws IOException { return fileSystem.readSymbolicLinkUnchecked(this); } /** * Create a hard link for the current path. * * @param link the path of the new link * @throws IOException if there was an error executing {@link FileSystem#createHardLink} */ public void createHardLink(Path link) throws IOException { fileSystem.createHardLink(link, this); } /** * Returns the canonical path for this path, by repeatedly replacing symbolic links with their * referents. Analogous to realpath(3). * * @return the canonical path for this path * @throws IOException if any symbolic link could not be resolved, or other error occurred (for * example, the path does not exist) */ public Path resolveSymbolicLinks() throws IOException { return fileSystem.resolveSymbolicLinks(this); } /** * Renames the file denoted by the current path to the location "target", not following symbolic * links. * *

Files cannot be atomically renamed across devices; copying is required. Use {@link * FileSystemUtils#copyFile} followed by {@link Path#delete}. * * @throws IOException if the rename failed for any reason */ public void renameTo(Path target) throws IOException { checkSameFileSystem(target); fileSystem.renameTo(this, target); } /** * Returns the size in bytes of the file denoted by the current path, following symbolic links. * *

The size of a directory or special file is undefined and should not be used. * * @throws FileNotFoundException if the file denoted by the current path does not exist * @throws IOException if the file's metadata could not be read, or some other error occurred */ public long getFileSize() throws IOException, FileNotFoundException { return fileSystem.getFileSize(this, true); } /** * Returns the size in bytes of the file denoted by the current path. * *

The size of directory or special file is undefined. The size of a symbolic link is the * length of the name of its referent. * * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the * link is deferenced until a file other than a symbol link is found * @throws FileNotFoundException if the file denoted by the current path does not exist * @throws IOException if the file's metadata could not be read, or some other error occurred */ public long getFileSize(Symlinks followSymlinks) throws IOException, FileNotFoundException { return fileSystem.getFileSize(this, followSymlinks.toBoolean()); } /** * Deletes the file denoted by this path, not following symbolic links. Returns normally iff the * file doesn't exist after the call: true if this call deleted the file, false if the file * already didn't exist. Throws an exception if the file could not be deleted for any reason. * * @return true iff the file was actually deleted by this call * @throws IOException if the deletion failed but the file was present prior to the call */ public boolean delete() throws IOException { return fileSystem.delete(this); } /** * Returns the last modification time of the file, in milliseconds since the UNIX epoch, of the * file denoted by the current path, following symbolic links. * *

Caveat: many filesystems store file times in seconds, so do not rely on the millisecond * precision. * * @throws IOException if the operation failed for any reason */ public long getLastModifiedTime() throws IOException { return fileSystem.getLastModifiedTime(this, true); } /** * Returns the last modification time of the file, in milliseconds since the UNIX epoch, of the * file denoted by the current path. * *

Caveat: many filesystems store file times in seconds, so do not rely on the millisecond * precision. * * @param followSymlinks if {@link Symlinks#FOLLOW}, and this path denotes a symbolic link, the * link is dereferenced until a file other than a symbolic link is found * @throws IOException if the modification time for the file could not be obtained for any reason */ public long getLastModifiedTime(Symlinks followSymlinks) throws IOException { return fileSystem.getLastModifiedTime(this, followSymlinks.toBoolean()); } /** * Sets the modification time of the file denoted by the current path. Follows symbolic links. If * newTime is -1, the current time according to the kernel is used; this may differ from the JVM's * clock. * *

Caveat: many filesystems store file times in seconds, so do not rely on the millisecond * precision. * * @param newTime time, in milliseconds since the UNIX epoch, or -1L, meaning use the kernel's * current time * @throws IOException if the modification time for the file could not be set for any reason */ public void setLastModifiedTime(long newTime) throws IOException { fileSystem.setLastModifiedTime(this, newTime); } /** * 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. */ public byte[] getxattr(String name) throws IOException { return fileSystem.getxattr(this, name); } /** * Gets a fast digest for the given path, or {@code null} if there isn't one available. The digest * should be suitable for detecting changes to the file. */ public byte[] getFastDigest() throws IOException { return fileSystem.getFastDigest(this); } /** Returns whether the given digest is a valid digest for the default system digest function. */ public boolean isValidDigest(byte[] digest) { return fileSystem.isValidDigest(digest); } /** * Returns the digest of the file denoted by the current path, following symbolic links. * * @return a new byte array containing the file's digest * @throws IOException if the digest could not be computed for any reason */ public byte[] getDigest() throws IOException { return fileSystem.getDigest(this); } /** * Return a string representation, as hexadecimal digits, of some hash of the directory. * *

The hash itself is computed according to the design document * https://github.com/bazelbuild/proposals/blob/master/designs/2018-07-13-repository-hashing.md * and takes enough information into account, to detect the typical non-reproducibility * of source-like repository rules, while leaving out what will change from invocation to * invocation of a repository rule (in particular file owners) and can reasonably be ignored * when considering if a repository is "the same source tree". * * @return a string representation of the bash of the directory * @throws IOException if the digest could not be computed for any reason */ public String getDirectoryDigest() throws IOException { List entries = new ArrayList(fileSystem.getDirectoryEntries(this)); Collections.sort(entries); Hasher hasher = fileSystem.getDigestFunction().getHashFunction().newHasher(); for (String entry : entries) { Path path = this.getChild(entry); FileStatus stat = path.stat(Symlinks.NOFOLLOW); hasher.putUnencodedChars(entry); if (stat.isFile()) { if (path.isExecutable()) { hasher.putChar('x'); } else { hasher.putChar('-'); } hasher.putBytes(path.getDigest()); } else if (stat.isDirectory()) { hasher.putChar('d').putUnencodedChars(path.getDirectoryDigest()); } else if (stat.isSymbolicLink()) { PathFragment link = path.readSymbolicLink(); if (link.isAbsolute()) { try { Path resolved = path.resolveSymbolicLinks(); if (resolved.isFile()) { if (resolved.isExecutable()) { hasher.putChar('x'); } else { hasher.putChar('-'); } hasher.putBytes(resolved.getDigest()); } else { // link to a non-file: include the link itself in the hash hasher.putChar('l').putUnencodedChars(link.toString()); } } catch (IOException e) { // dangling link: include the link itself in the hash hasher.putChar('l').putUnencodedChars(link.toString()); } } else { // relative link: include the link itself in the hash hasher.putChar('l').putUnencodedChars(link.toString()); } } else { // Neither file, nor directory, nor symlink. So do not include further information // in the hash, asuming it will not be used during the BUILD anyway. hasher.putChar('s'); } } return hasher.hash().toString(); } /** * Opens the file denoted by this path, following symbolic links, for reading, and returns an * input stream to it. * * @throws IOException if the file was not found or could not be opened for reading */ public InputStream getInputStream() throws IOException { return fileSystem.getInputStream(this); } /** * Returns a java.io.File representation of this path. * *

Caveat: the result may be useless if this path's getFileSystem() is not * the UNIX filesystem. */ public File getPathFile() { return new File(getPathString()); } /** * Returns true if the file denoted by the current path, following symbolic links, is writable for * the current user. * * @throws FileNotFoundException if the file does not exist, a dangling symbolic link was * encountered, or the file's metadata could not be read */ public boolean isWritable() throws IOException, FileNotFoundException { return fileSystem.isWritable(this); } /** * Sets the read permissions of the file denoted by the current path, following symbolic links. * Permissions apply to the current user. * * @param readable if true, the file is set to readable; otherwise the file is made non-readable * @throws FileNotFoundException if the file does not exist * @throws IOException If the action cannot be taken (ie. permissions) */ public void setReadable(boolean readable) throws IOException, FileNotFoundException { fileSystem.setReadable(this, readable); } /** * Sets the write permissions of the file denoted by the current path, following symbolic links. * Permissions apply to the current user. * *

TODO(bazel-team): (2009) what about owner/group/others? * * @param writable if true, the file is set to writable; otherwise the file is made non-writable * @throws FileNotFoundException if the file does not exist * @throws IOException If the action cannot be taken (ie. permissions) */ public void setWritable(boolean writable) throws IOException, FileNotFoundException { fileSystem.setWritable(this, writable); } /** * Returns true iff the file specified by the current path, following symbolic links, is * executable by the current user. * * @throws FileNotFoundException if the file does not exist or a dangling symbolic link was * encountered * @throws IOException if some other I/O error occurred */ public boolean isExecutable() throws IOException, FileNotFoundException { return fileSystem.isExecutable(this); } /** * Returns true iff the file specified by the current path, following symbolic links, is readable * by the current user. * * @throws FileNotFoundException if the file does not exist or a dangling symbolic link was * encountered * @throws IOException if some other I/O error occurred */ public boolean isReadable() throws IOException, FileNotFoundException { return fileSystem.isReadable(this); } /** * Sets the execute permission on the file specified by the current path, following symbolic * links. Permissions apply to the current user. * * @throws FileNotFoundException if the file does not exist or a dangling symbolic link was * encountered * @throws IOException if the metadata change failed, for example because of permissions */ public void setExecutable(boolean executable) throws IOException, FileNotFoundException { fileSystem.setExecutable(this, executable); } /** * Sets the permissions on the file specified by the current path, following symbolic links. If * permission changes on this path's {@link FileSystem} are slow (e.g. one syscall per change), * this method should aim to be faster than setting each permission individually. If this path's * {@link FileSystem} does not support group and others permissions, those bits will be ignored. * * @throws FileNotFoundException if the file does not exist or a dangling symbolic link was * encountered * @throws IOException if the metadata change failed, for example because of permissions */ public void chmod(int mode) throws IOException { fileSystem.chmod(this, mode); } public void prefetchPackageAsync(int maxDirs) { fileSystem.prefetchPackageAsync(this, maxDirs); } private void checkSameFileSystem(Path that) { if (this.fileSystem != that.fileSystem) { throw new IllegalArgumentException( "Files are on different filesystems: " + this + ", " + that); } } private void writeObject(ObjectOutputStream out) throws IOException { Preconditions.checkState( fileSystem == fileSystemForSerialization, "%s %s", fileSystem, fileSystemForSerialization); out.writeUTF(path); } private void readObject(ObjectInputStream in) throws IOException { path = in.readUTF(); fileSystem = fileSystemForSerialization; driveStrLength = OS.getDriveStrLength(path); } }