// 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. * *
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()); } }