| // 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.unix; |
| |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.devtools.build.lib.bugreport.BugReport; |
| import com.google.devtools.build.lib.jni.JniLoader; |
| import com.google.devtools.build.lib.util.Blocker; |
| import com.google.devtools.build.lib.vfs.FileStatus; |
| import java.io.FileNotFoundException; |
| import java.io.IOException; |
| |
| /** |
| * 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. |
| */ |
| final class NativePosixFiles { |
| private NativePosixFiles() {} |
| |
| static { |
| JniLoader.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 |
| */ |
| 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. |
| */ |
| 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. |
| */ |
| static native void symlink(String oldpath, String newpath) throws IOException; |
| |
| /** |
| * Native wrapper around POSIX link(2) syscall. |
| * |
| * @param oldpath the file to link to |
| * @param newpath the new path for the link |
| * @throws IOException iff the link() syscall failed. |
| */ |
| static native void link(String oldpath, String newpath) throws IOException; |
| |
| /** How stat() and lstat() should handle errors. */ |
| enum StatErrorHandling { |
| /** Always throw an exception. */ |
| ALWAYS_THROW('a'), |
| /** Throw an exception unless the error is ENOENT/ENOTDIR, in which case return null. */ |
| THROW_UNLESS_NOT_FOUND('f'), |
| /* Never throw an exception. Return null instead. */ |
| NEVER_THROW('n'); |
| |
| private final char code; |
| |
| private StatErrorHandling(char code) { |
| this.code = code; |
| } |
| |
| private char getCode() { |
| return code; |
| } |
| } |
| |
| static FileStatus stat(String path, StatErrorHandling errorHandling) throws IOException { |
| return stat(path, errorHandling.getCode()); |
| } |
| |
| /** |
| * Native wrapper around POSIX stat(2) syscall. |
| * |
| * @param path the file to stat. |
| * @param errorHandling how to handle errors. |
| * @return a FileStatus instance containing the metadata. |
| * @throws IOException if the stat() syscall failed. |
| */ |
| private static native FileStatus stat(String path, char errorHandling) throws IOException; |
| |
| static FileStatus lstat(String path, StatErrorHandling errorHandling) throws IOException { |
| return lstat(path, errorHandling.getCode()); |
| } |
| |
| /** |
| * Native wrapper around POSIX lstat(2) syscall. |
| * |
| * @param path the file to lstat. |
| * @param errorHandling how to handle errors. |
| * @return a FileStatus instance containing the metadata. |
| * @throws IOException if the lstat() syscall failed. |
| */ |
| private static native FileStatus lstat(String path, char errorHandling) throws IOException; |
| |
| /** |
| * Native wrapper around POSIX utimensat(2) syscall. |
| * |
| * <p>Note that, even though utimensat(2) supports up to nanosecond precision, this interface only |
| * allows millisecond precision, which is what Bazel uses internally. |
| * |
| * @param path the file whose modification time should be changed. |
| * @param now if true, ignore {@code epochMilli} and use the current time. |
| * @param epochMilli the file modification time in milliseconds since the UNIX epoch. |
| * @throws IOException if the operation failed. |
| */ |
| static native void utimensat(String path, boolean now, long epochMilli) throws IOException; |
| |
| /** |
| * Native wrapper around POSIX mkdir(2) syscall. |
| * |
| * <p>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. |
| */ |
| static native boolean mkdir(String path, int mode) throws IOException; |
| |
| /** |
| * Makes sure a writable directory exists at a given path. Returns whether a new directory was |
| * created. |
| * |
| * <p>Unlike {@link #mkdir}, it fails if a file/symlink at a given path already exists. If a |
| * directory is already present, it will make sure it is writable and return false. |
| */ |
| static native boolean mkdirWritable(String path); |
| |
| /** |
| * Implements (effectively) mkdir -p. |
| * |
| * @param path the directory to recursively create. |
| * @param mode the mode with which to create the directories. |
| * @throws IOException if the directory creation failed for any reason. |
| */ |
| static native void mkdirs(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. |
| */ |
| static String[] readdir(String path) throws IOException { |
| return readdir(path, ReadTypes.NONE).names; |
| } |
| |
| /** |
| * 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. |
| */ |
| static Dirents readdir(String path, ReadTypes readTypes) throws IOException { |
| var comp = Blocker.begin(); |
| try { |
| // Passing enums to native code is possible, but onerous; we use a char instead. |
| return readdir(path, readTypes.getCode()); |
| } finally { |
| Blocker.end(comp); |
| } |
| } |
| |
| private static native Dirents readdir(String path, char typeCode) throws IOException; |
| |
| /** |
| * An enum for specifying now the types of the individual entries returned by {@link |
| * #readdir(String, ReadTypes)} is to be returned. |
| */ |
| 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. |
| */ |
| static final class Dirents { |
| |
| /** The type of the directory entry. */ |
| 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 possible types are: |
| * |
| * <ul> |
| * <li>'d': a subdirectory |
| * <li>'f': a regular file |
| * <li>'s': a symlink, only returned for {@link ReadTypes.NOFOLLOW} |
| * <li>'?': anything else, including: |
| * <ul> |
| * <li>a special file |
| * <li>a nonexistent symlink target |
| * <li>an error occurred while determining the file type, for example because of a |
| * symlink loop |
| * </ul> |
| * </ul> |
| * |
| * <p>This is intentionally a byte array rather than a array of enums to save memory. |
| */ |
| private final byte[] types; |
| |
| /** called from JNI */ |
| Dirents(String[] names, byte[] types) { |
| this.names = names; |
| this.types = types; |
| } |
| |
| int size() { |
| return names.length; |
| } |
| |
| boolean hasTypes() { |
| return types != null; |
| } |
| |
| String getName(int i) { |
| return names[i]; |
| } |
| |
| Type getType(int i) { |
| return Type.forChar((char) types[i]); |
| } |
| } |
| |
| /** |
| * 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. |
| */ |
| 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. |
| */ |
| static native boolean remove(String path) throws IOException; |
| |
| /** |
| * Native wrapper around POSIX mkfifo(3) C library call. |
| * |
| * @param path the name of the pipe to create. |
| * @param mode the mode with which to create the pipe. |
| * @throws IOException if the mkfifo failed. |
| */ |
| @VisibleForTesting |
| static native void mkfifo(String path, int mode) 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. |
| */ |
| 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. |
| */ |
| static native byte[] lgetxattr(String path, String name) throws IOException; |
| |
| /** |
| * Deletes all directory trees recursively beneath the given path, which is expected to be a |
| * directory. Does not remove the top directory. |
| * |
| * @param dir the directory hierarchy to remove |
| * @throws IOException if the hierarchy cannot be removed successfully or if the given path is not |
| * a directory |
| */ |
| static native void deleteTreesBelow(String dir) throws IOException; |
| |
| /** |
| * Open a file descriptor for writing. |
| * |
| * <p>This is a low level API. The caller is responsible for calling {@link close} on the returned |
| * file descriptor. |
| * |
| * @param path file to open |
| * @param append whether to open is append mode |
| */ |
| static native int openWrite(String path, boolean append) throws FileNotFoundException; |
| |
| /** Write a segment of data to a file descriptor. */ |
| static native int write(int fd, byte[] data, int off, int len) throws IOException; |
| |
| /** |
| * Close a file descriptor. Additionally, accept and ignore an object; this can be used to keep a |
| * reference alive. |
| */ |
| static native int close(int fd, Object ignored) throws IOException; |
| |
| /** Logs a path string that does not have a Latin-1 coder. Called from JNI. */ |
| private static void logBadPath(String path) { |
| BugReport.sendNonFatalBugReport( |
| new IllegalStateException("Path string does not have a Latin-1 coder: %s".formatted(path))); |
| } |
| } |