aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/unix/FilesystemUtils.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/google/devtools/build/lib/unix/FilesystemUtils.java')
-rw-r--r--src/main/java/com/google/devtools/build/lib/unix/FilesystemUtils.java442
1 files changed, 442 insertions, 0 deletions
diff --git a/src/main/java/com/google/devtools/build/lib/unix/FilesystemUtils.java b/src/main/java/com/google/devtools/build/lib/unix/FilesystemUtils.java
new file mode 100644
index 0000000000..462ed9c22a
--- /dev/null
+++ b/src/main/java/com/google/devtools/build/lib/unix/FilesystemUtils.java
@@ -0,0 +1,442 @@
+// Copyright 2014 Google Inc. 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.unix;
+
+import com.google.common.hash.HashCode;
+import com.google.devtools.build.lib.UnixJniLoader;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.logging.Level;
+import java.util.logging.LogManager;
+import java.util.logging.Logger;
+
+/**
+ * Utility methods for access to UNIX filesystem calls not exposed by the Java
+ * SDK. Exception messages are selected to be consistent with those generated
+ * by the java.io package where appropriate--see package javadoc for details.
+ */
+public final class FilesystemUtils {
+
+ private FilesystemUtils() {}
+
+ /**
+ * Returns true iff the file identified by 'path' is a symbolic link. Has
+ * similar semantics to POSIX stat(2) syscall, with all errors being mapped to
+ * a false return.
+ *
+ * @param path the file of interest
+ * @return true iff path exists, is accessible and is a symlink
+ */
+ public static boolean isSymbolicLink(File path) {
+ try {
+ return lstat(path.toString()).isSymbolicLink();
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Returns true iff the file identified by 'path' is a directory. Has
+ * similar semantics to POSIX stat(2) syscall, with all errors being mapped to
+ * a false return.
+ *
+ * @param path the file of interest
+ * @return true iff path exists, is accessible and is a symlink
+ */
+ public static boolean isDirectory(String path) {
+ try {
+ return lstat(path).isDirectory();
+ } catch (IOException e) {
+ return false;
+ }
+ }
+
+
+ /**
+ * Marks the file or directory {@code path} as executable. (Non-atomic)
+ *
+ * @see File#setReadOnly
+ *
+ * @param path the file of interest
+ * @throws FileAccessException if path can't be accessed
+ * @throws FileNotFoundException if path doesn't exist
+ * @throws IOException for other filesystem or path errors
+ */
+ public static void setExecutable(File path) throws IOException {
+ String p = path.toString();
+ chmod(p, stat(p).getPermissions() | FileStatus.S_IEXEC);
+ }
+
+ /**
+ * Marks the file or directory {@code path} as owner writable. (Non-atomic)
+ *
+ * @see File#setReadOnly
+ *
+ * @param path the file of interest
+ * @throws FileAccessException if path can't be accessed
+ * @throws FileNotFoundException if path doesn't exist
+ * @throws IOException for other filesystem or path errors
+ */
+ public static void setWritable(File path) throws IOException {
+ String p = path.toString();
+ chmod(p, stat(p).getPermissions() | FileStatus.S_IWUSR);
+ }
+
+ /**
+ * Changes permissions of a file.
+ *
+ * @param path the file whose mode to change.
+ * @param mode the mode bits within 07777, interpreted according to
+ * long-standing UNIX tradition.
+ * @throws IOException if the chmod() syscall failed.
+ */
+ public static void chmod(File path, int mode) throws IOException {
+ int mask = FileStatus.S_ISUID |
+ FileStatus.S_ISGID |
+ FileStatus.S_ISVTX |
+ FileStatus.S_IRWXA;
+ chmod(path.toString(), mode & mask);
+ }
+
+ /*
+ * Native-based implementation
+ */
+
+ static {
+ if (!java.nio.charset.Charset.defaultCharset().name().equals("ISO-8859-1")) {
+ // Defer the Logger call, so we don't deadlock if this is called from Logger
+ // initialization code.
+ new Thread() {
+ @Override
+ public void run() {
+ // wait (if necessary) until the logging system is initialized
+ synchronized (LogManager.getLogManager()) {}
+ Logger.getLogger("com.google.devtools.build.lib.unix.FilesystemUtils").log(Level.FINE,
+ "WARNING: Default character set is not latin1; java.io.File and " +
+ "com.google.devtools.build.lib.unix.FilesystemUtils will represent some filenames " +
+ "differently.");
+ }
+ }.start();
+ }
+ UnixJniLoader.loadJni();
+ }
+
+ /**
+ * Native wrapper around Linux readlink(2) call.
+ *
+ * @param path the file of interest
+ * @return the pathname to which the symbolic link 'path' links
+ * @throws IOException iff the readlink() call failed
+ */
+ public static native String readlink(String path) throws IOException;
+
+ /**
+ * Native wrapper around POSIX chmod(2) syscall: Changes the file access
+ * permissions of 'path' to 'mode'.
+ *
+ * @param path the file of interest
+ * @param mode the POSIX type and permission mode bits to set
+ * @throws IOException iff the chmod() call failed.
+ */
+ public static native void chmod(String path, int mode) throws IOException;
+
+ /**
+ * Native wrapper around POSIX symlink(2) syscall.
+ *
+ * @param oldpath the file to link to
+ * @param newpath the new path for the link
+ * @throws IOException iff the symlink() syscall failed.
+ */
+ public static native void symlink(String oldpath, String newpath)
+ throws IOException;
+
+ /**
+ * Native wrapper around POSIX stat(2) syscall.
+ *
+ * @param path the file to stat.
+ * @return a FileStatus instance containing the metadata.
+ * @throws IOException if the stat() syscall failed.
+ */
+ public static native FileStatus stat(String path) throws IOException;
+
+ /**
+ * Native wrapper around POSIX lstat(2) syscall.
+ *
+ * @param path the file to lstat.
+ * @return a FileStatus instance containing the metadata.
+ * @throws IOException if the lstat() syscall failed.
+ */
+ public static native FileStatus lstat(String path) throws IOException;
+
+ /**
+ * Native wrapper around POSIX stat(2) syscall.
+ *
+ * @param path the file to stat.
+ * @return an ErrnoFileStatus instance containing the metadata.
+ * If there was an error, the return value's hasError() method
+ * will return true, and all stat information is undefined.
+ */
+ public static native ErrnoFileStatus errnoStat(String path);
+
+ /**
+ * Native wrapper around POSIX lstat(2) syscall.
+ *
+ * @param path the file to lstat.
+ * @return an ErrnoFileStatus instance containing the metadata.
+ * If there was an error, the return value's hasError() method
+ * will return true, and all stat information is undefined.
+ */
+ public static native ErrnoFileStatus errnoLstat(String path);
+
+ /**
+ * Native wrapper around POSIX utime(2) syscall.
+ *
+ * Note: negative file times are interpreted as unsigned time_t.
+ *
+ * @param path the file whose times to change.
+ * @param now if true, ignore actime/modtime parameters and use current time.
+ * @param actime the file access time in seconds since the UNIX epoch.
+ * @param modtime the file modification time in seconds since the UNIX epoch.
+ * @throws IOException if the utime() syscall failed.
+ */
+ public static native void utime(String path, boolean now,
+ int actime, int modtime) throws IOException;
+
+ /**
+ * Native wrapper around POSIX mkdir(2) syscall.
+ *
+ * Caveat: errno==EEXIST is mapped to the return value "false", not
+ * IOException. It requires an additional stat() to determine if mkdir
+ * failed because the directory already exists.
+ *
+ * @param path the directory to create.
+ * @param mode the mode with which to create the directory.
+ * @return true if the directory was successfully created; false if the
+ * system call returned EEXIST because some kind of a file (not necessarily
+ * a directory) already exists.
+ * @throws IOException if the mkdir() syscall failed for any other reason.
+ */
+ public static native boolean mkdir(String path, int mode)
+ throws IOException;
+
+ /**
+ * Native wrapper around POSIX opendir(2)/readdir(3)/closedir(3) syscall.
+ *
+ * @param path the directory to read.
+ * @return the list of directory entries in the order they were returned by
+ * the system, excluding "." and "..".
+ * @throws IOException if the call to opendir failed for any reason.
+ */
+ public static String[] readdir(String path) throws IOException {
+ return readdir(path, ReadTypes.NONE).names;
+ }
+
+ /**
+ * An enum for specifying now the types of the individual entries returned by
+ * {@link #readdir(String, ReadTypes)} is to be returned.
+ */
+ public enum ReadTypes {
+ NONE('n'), // Do not read types
+ NOFOLLOW('d'), // Do not follow symlinks
+ FOLLOW('f'); // Follow symlinks; never returns "SYMLINK" and returns "UNKNOWN" when dangling
+
+ private final char code;
+
+ private ReadTypes(char code) {
+ this.code = code;
+ }
+
+ private char getCode() {
+ return code;
+ }
+ }
+
+ /**
+ * A compound return type for readdir(), analogous to struct dirent[] in C. A low memory profile
+ * is critical for this class, as instances are expected to be kept around for caching for
+ * potentially a long time.
+ */
+ public static final class Dirents {
+
+ /**
+ * The type of the directory entry.
+ */
+ public enum Type {
+ FILE,
+ DIRECTORY,
+ SYMLINK,
+ UNKNOWN;
+
+ private static Type forChar(char c) {
+ if (c == 'f') {
+ return Type.FILE;
+ } else if (c == 'd') {
+ return Type.DIRECTORY;
+ } else if (c == 's') {
+ return Type.SYMLINK;
+ } else {
+ return Type.UNKNOWN;
+ }
+ }
+ }
+
+ /** The names of the entries in a directory. */
+ private final String[] names;
+ /**
+ * An optional (nullable) array of entry types, corresponding positionally
+ * to the "names" field. The types are:
+ * 'd': a subdirectory
+ * 'f': a regular file
+ * 's': a symlink (only returned with {@code NOFOLLOW})
+ * '?': anything else
+ * Note that unlike libc, this implementation of readdir() follows
+ * symlinks when determining these types.
+ *
+ * <p>This is intentionally a byte array rather than a array of enums to save memory.
+ */
+ private final byte[] types;
+
+ /** called from JNI */
+ public Dirents(String[] names, byte[] types) {
+ this.names = names;
+ this.types = types;
+ }
+
+ public int size() {
+ return names.length;
+ }
+
+ public boolean hasTypes() {
+ return types != null;
+ }
+
+ public String getName(int i) {
+ return names[i];
+ }
+
+ public Type getType(int i) {
+ return Type.forChar((char) types[i]);
+ }
+ }
+
+ /**
+ * Native wrapper around POSIX opendir(2)/readdir(3)/closedir(3) syscall.
+ *
+ * @param path the directory to read.
+ * @param readTypes How the types of individual entries should be returned. If {@code NONE},
+ * the "types" field in the result will be null.
+ * @return a Dirents object, containing "names", the list of directory entries
+ * (excluding "." and "..") in the order they were returned by the system,
+ * and "types", an array of entry types (file, directory, etc) corresponding
+ * positionally to "names".
+ * @throws IOException if the call to opendir failed for any reason.
+ */
+ public static Dirents readdir(String path, ReadTypes readTypes) throws IOException {
+ // Passing enums to native code is possible, but onerous; we use a char instead.
+ return readdir(path, readTypes.getCode());
+ }
+
+ private static native Dirents readdir(String path, char typeCode)
+ throws IOException;
+
+ /**
+ * Native wrapper around POSIX rename(2) syscall.
+ *
+ * @param oldpath the source location.
+ * @param newpath the destination location.
+ * @throws IOException if the rename failed for any reason.
+ */
+ public static native void rename(String oldpath, String newpath)
+ throws IOException;
+
+ /**
+ * Native wrapper around POSIX remove(3) C library call.
+ *
+ * @param path the file or directory to remove.
+ * @return true iff the file was actually deleted by this call.
+ * @throws IOException if the remove failed, but the file was present prior to the call.
+ */
+ public static native boolean remove(String path) throws IOException;
+
+ /********************************************************************
+ * *
+ * Linux extended file attributes *
+ * *
+ ********************************************************************/
+
+ /**
+ * Native wrapper around Linux getxattr(2) syscall.
+ *
+ * @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).
+ * @throws IOException if the call failed for any other reason.
+ */
+ public static native byte[] getxattr(String path, String name)
+ throws IOException;
+
+ /**
+ * Native wrapper around Linux lgetxattr(2) syscall. (Like getxattr, but
+ * does not follow symbolic links.)
+ *
+ * @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).
+ * @throws IOException if the call failed for any other reason.
+ */
+ public static native byte[] lgetxattr(String path, String name)
+ throws IOException;
+
+ /**
+ * Returns the MD5 digest of the specified file, following symbolic links.
+ *
+ * @param path the file whose MD5 digest is required.
+ * @return the MD5 digest, as a 16-byte array.
+ * @throws IOException if the call failed for any reason.
+ */
+ static native byte[] md5sumAsBytes(String path) throws IOException;
+
+ /**
+ * Returns the MD5 digest of the specified file, following symbolic links.
+ *
+ * @param path the file whose MD5 digest is required.
+ * @return the MD5 digest, as a {@link HashCode}
+ * @throws IOException if the call failed for any reason.
+ */
+ public static HashCode md5sum(String path) throws IOException {
+ return HashCode.fromBytes(md5sumAsBytes(path));
+ }
+
+ /**
+ * Removes entire directory tree. Doesn't follow symlinks.
+ *
+ * @param path the file or directory to remove.
+ * @throws IOException if the remove failed.
+ */
+ public static void rmTree(String path) throws IOException {
+ if (isDirectory(path)) {
+ String[] contents = readdir(path);
+ for (String entry : contents) {
+ rmTree(path + "/" + entry);
+ }
+ }
+ remove(path.toString());
+ }
+}