blob: cf8e2fb99c0cf577b2e1413dfa3c0057468c0443 [file] [log] [blame]
// 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.common.flogger.GoogleLogger;
import com.google.devtools.build.lib.jni.JniLoader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.logging.LogManager;
/**
* 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 NativePosixFiles {
private NativePosixFiles() {}
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(
() -> {
// wait (if necessary) until the logging system is initialized
synchronized (LogManager.getLogManager()) {
}
@SuppressWarnings("FloggerRequiredModifiers")
GoogleLogger logger = GoogleLogger.forEnclosingClass();
logger.atFine().log(
"WARNING: Default character set is not latin1; java.io.File and"
+ " com.google.devtools.build.lib.unix.FilesystemUtils will represent"
+ " some filenames differently.");
})
.start();
}
JniLoader.loadJni();
initJNIClasses();
}
/**
* 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 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.
*/
public static native void link(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 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 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;
/**
* 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.
*/
public 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.
*/
public 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.
*/
public 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.
*/
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;
/**
* 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 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;
/**
* 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
public 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.
*/
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;
/**
* 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
*/
public 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
*/
public static native int openWrite(String path, boolean append) throws FileNotFoundException;
/** Write a segment of data to a file descriptor. */
public 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.
*/
public static native int close(int fd, Object ignored) throws IOException;
private static native void initJNIClasses();
}