diff options
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.java | 442 |
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()); + } +} |