blob: c9ec3dd5d656e59b6e23c3cbd65e819359cce804 [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.
#include "src/main/native/unix_jni.h"
#include <jni.h>
#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <string.h>
#include <sys/resource.h>
#include <sys/stat.h>
#include <sys/syscall.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <utime.h>
#include <string>
#include <vector>
#include "src/main/native/macros.h"
#include "src/main/cpp/util/md5.h"
#include "src/main/cpp/util/port.h"
using blaze_util::Md5Digest;
////////////////////////////////////////////////////////////////////////
// Latin1 <--> java.lang.String conversion functions.
// Derived from similar routines in Sun JDK. See:
// j2se/src/solaris/native/java/io/UnixFileSystem_md.c
// j2se/src/share/native/common/jni_util.c
//
// Like the Sun JDK in its usual configuration, we assume all UNIX
// filenames are Latin1 encoded.
/**
* Returns a new Java String for the specified Latin1 characters.
*/
static jstring NewStringLatin1(JNIEnv *env, const char *str) {
int len = strlen(str);
jchar buf[512];
jchar *str1;
if (len > 512) {
str1 = new jchar[len];
} else {
str1 = buf;
}
for (int i = 0; i < len ; i++) {
str1[i] = (unsigned char) str[i];
}
jstring result = env->NewString(str1, len);
if (str1 != buf) {
delete[] str1;
}
return result;
}
static jfieldID String_coder_field;
static jfieldID String_value_field;
static bool CompactStringsEnabled(JNIEnv *env) {
if (jclass klass = env->FindClass("java/lang/String")) {
if (jfieldID csf = env->GetStaticFieldID(klass, "COMPACT_STRINGS", "Z")) {
if (env->GetStaticBooleanField(klass, csf)) {
if ((String_coder_field = env->GetFieldID(klass, "coder", "B"))) {
if ((String_value_field = env->GetFieldID(klass, "value", "[B"))) {
return true;
}
}
}
}
}
env->ExceptionClear();
return false;
}
/**
* Returns a nul-terminated Latin1-encoded byte array for the
* specified Java string, or null on failure. Unencodable characters
* are replaced by '?'. Must be followed by a call to
* ReleaseStringLatin1Chars.
*/
static char *GetStringLatin1Chars(JNIEnv *env, jstring jstr) {
jint len = env->GetStringLength(jstr);
// Fast path for latin1 strings.
static bool cs_enabled = CompactStringsEnabled(env);
const int LATIN1 = 0;
if (cs_enabled && env->GetByteField(jstr, String_coder_field) == LATIN1) {
char *result = new char[len + 1];
if (jobject jvalue = env->GetObjectField(jstr, String_value_field)) {
env->GetByteArrayRegion((jbyteArray)jvalue, 0, len, (jbyte *)result);
}
result[len] = 0;
return result;
}
const jchar *str = env->GetStringCritical(jstr, NULL);
if (str == NULL) {
return NULL;
}
char *result = new char[len + 1];
for (int i = 0; i < len; i++) {
jchar unicode = str[i]; // (unsigned)
result[i] = unicode <= 0x00ff ? unicode : '?';
}
result[len] = 0;
env->ReleaseStringCritical(jstr, str);
return result;
}
/**
* Release the Latin1 chars returned by a prior call to
* GetStringLatin1Chars.
*/
static void ReleaseStringLatin1Chars(const char *s) {
delete[] s;
}
////////////////////////////////////////////////////////////////////////
// See unix_jni.h.
void PostException(JNIEnv *env, int error_number, const std::string& message) {
// Keep consistent with package-info.html!
//
// See /usr/include/asm/errno.h for UNIX error messages.
// Select the most appropriate Java exception for a given UNIX error number.
// (Consistent with errors generated by java.io package.)
const char *exception_classname;
switch (error_number) {
case EFAULT: // Illegal pointer--not likely
case EBADF: // Bad file number
exception_classname = "java/lang/IllegalArgumentException";
break;
case ETIMEDOUT: // Local socket timed out
exception_classname = "java/net/SocketTimeoutException";
break;
case ENOENT: // No such file or directory
exception_classname = "java/io/FileNotFoundException";
break;
case EACCES: // Permission denied
exception_classname =
"com/google/devtools/build/lib/vfs/FileAccessException";
break;
case EPERM: // Operation not permitted
exception_classname =
"com/google/devtools/build/lib/unix/FilePermissionException";
break;
case EINTR: // Interrupted system call
exception_classname = "java/io/InterruptedIOException";
break;
case ENOMEM: // Out of memory
exception_classname = "java/lang/OutOfMemoryError";
break;
case ENOSYS: // Function not implemented
case ENOTSUP: // Operation not supported on transport endpoint
// (aka EOPNOTSUPP)
exception_classname = "java/lang/UnsupportedOperationException";
break;
case ENAMETOOLONG: // File name too long
case ENODATA: // No data available
case EINVAL: // Invalid argument
case EMULTIHOP: // Multihop attempted
case ENOLINK: // Link has been severed
case EIO: // I/O error
case EAGAIN: // Try again
case EFBIG: // File too large
case EPIPE: // Broken pipe
case ENOSPC: // No space left on device
case EXDEV: // Cross-device link
case EROFS: // Read-only file system
case EEXIST: // File exists
case EMLINK: // Too many links
case ELOOP: // Too many symbolic links encountered
case EISDIR: // Is a directory
case ENOTDIR: // Not a directory
case ENOTEMPTY: // Directory not empty
case EBUSY: // Device or resource busy
case ENFILE: // File table overflow
case EMFILE: // Too many open files
default:
exception_classname = "java/io/IOException";
}
jclass exception_class = env->FindClass(exception_classname);
if (exception_class != NULL) {
env->ThrowNew(exception_class, message.c_str());
} else {
abort(); // panic!
}
}
// Throws RuntimeExceptions for IO operations which fail unexpectedly.
// See package-info.html.
// Returns true iff an exception was thrown.
static bool PostRuntimeException(JNIEnv *env, int error_number,
const char *file_path) {
const char *exception_classname;
switch (error_number) {
case EFAULT: // Illegal pointer--not likely
case EBADF: // Bad file number
exception_classname = "java/lang/IllegalArgumentException";
break;
case ENOMEM: // Out of memory
exception_classname = "java/lang/OutOfMemoryError";
break;
case ENOTSUP: // Operation not supported on transport endpoint
// (aka EOPNOTSUPP)
exception_classname = "java/lang/UnsupportedOperationException";
break;
default:
exception_classname = NULL;
}
if (exception_classname == NULL) {
return false;
}
jclass exception_class = env->FindClass(exception_classname);
if (exception_class != NULL) {
std::string message(file_path);
message += " (";
message += ErrorMessage(error_number);
message += ")";
env->ThrowNew(exception_class, message.c_str());
return true;
} else {
abort(); // panic!
return false; // Not reachable.
}
}
// See unix_jni.h.
void PostFileException(JNIEnv *env, int error_number, const char *filename) {
::PostException(env, error_number,
std::string(filename) + " (" + ErrorMessage(error_number)
+ ")");
}
// See unix_jni.h.
void PostSystemException(JNIEnv *env, int error_number, const char *function,
const char *name) {
::PostException(env, error_number, std::string(function) + "(" +
std::string(name) + ")" + " (" +
ErrorMessage(error_number) + ")");
}
// TODO(bazel-team): split out all the FileSystem class's native methods
// into a separate source file, fsutils.cc.
extern "C" JNIEXPORT jstring JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_readlink(JNIEnv *env,
jclass clazz,
jstring path) {
const char *path_chars = GetStringLatin1Chars(env, path);
char target[PATH_MAX] = "";
jstring r = NULL;
if (readlink(path_chars, target, arraysize(target)) == -1) {
::PostFileException(env, errno, path_chars);
} else {
r = NewStringLatin1(env, target);
}
ReleaseStringLatin1Chars(path_chars);
return r;
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_chmod(JNIEnv *env,
jclass clazz,
jstring path,
jint mode) {
const char *path_chars = GetStringLatin1Chars(env, path);
if (chmod(path_chars, static_cast<int>(mode)) == -1) {
::PostFileException(env, errno, path_chars);
}
ReleaseStringLatin1Chars(path_chars);
}
static void link_common(JNIEnv *env,
jstring oldpath,
jstring newpath,
int (*link_function)(const char *, const char *)) {
const char *oldpath_chars = GetStringLatin1Chars(env, oldpath);
const char *newpath_chars = GetStringLatin1Chars(env, newpath);
if (link_function(oldpath_chars, newpath_chars) == -1) {
::PostFileException(env, errno, newpath_chars);
}
ReleaseStringLatin1Chars(oldpath_chars);
ReleaseStringLatin1Chars(newpath_chars);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_link(JNIEnv *env,
jclass clazz,
jstring oldpath,
jstring newpath) {
link_common(env, oldpath, newpath, ::link);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_symlink(JNIEnv *env,
jclass clazz,
jstring oldpath,
jstring newpath) {
link_common(env, oldpath, newpath, ::symlink);
}
static jobject NewFileStatus(JNIEnv *env,
const portable_stat_struct &stat_ref) {
static jclass file_status_class = NULL;
if (file_status_class == NULL) { // note: harmless race condition
jclass local = env->FindClass("com/google/devtools/build/lib/unix/FileStatus");
CHECK(local != NULL);
file_status_class = static_cast<jclass>(env->NewGlobalRef(local));
}
static jmethodID method = NULL;
if (method == NULL) { // note: harmless race condition
method = env->GetMethodID(file_status_class, "<init>", "(IIIIIIIJIJ)V");
CHECK(method != NULL);
}
return env->NewObject(
file_status_class, method, stat_ref.st_mode,
StatSeconds(stat_ref, STAT_ATIME), StatNanoSeconds(stat_ref, STAT_ATIME),
StatSeconds(stat_ref, STAT_MTIME), StatNanoSeconds(stat_ref, STAT_MTIME),
StatSeconds(stat_ref, STAT_CTIME), StatNanoSeconds(stat_ref, STAT_CTIME),
static_cast<jlong>(stat_ref.st_size),
static_cast<int>(stat_ref.st_dev), static_cast<jlong>(stat_ref.st_ino));
}
static jobject NewErrnoFileStatus(JNIEnv *env,
int saved_errno,
const portable_stat_struct &stat_ref) {
static jclass errno_file_status_class = NULL;
if (errno_file_status_class == NULL) { // note: harmless race condition
jclass local = env->FindClass("com/google/devtools/build/lib/unix/ErrnoFileStatus");
CHECK(local != NULL);
errno_file_status_class = static_cast<jclass>(env->NewGlobalRef(local));
}
static jmethodID no_error_ctor = NULL;
if (no_error_ctor == NULL) { // note: harmless race condition
no_error_ctor = env->GetMethodID(errno_file_status_class,
"<init>", "(IIIIIIIJIJ)V");
CHECK(no_error_ctor != NULL);
}
static jmethodID errorno_ctor = NULL;
if (errorno_ctor == NULL) { // note: harmless race condition
errorno_ctor = env->GetMethodID(errno_file_status_class, "<init>", "(I)V");
CHECK(errorno_ctor != NULL);
}
if (saved_errno != 0) {
return env->NewObject(errno_file_status_class, errorno_ctor, saved_errno);
}
return env->NewObject(
errno_file_status_class, no_error_ctor, stat_ref.st_mode,
StatSeconds(stat_ref, STAT_ATIME), StatNanoSeconds(stat_ref, STAT_ATIME),
StatSeconds(stat_ref, STAT_MTIME), StatNanoSeconds(stat_ref, STAT_MTIME),
StatSeconds(stat_ref, STAT_CTIME), StatNanoSeconds(stat_ref, STAT_CTIME),
static_cast<jlong>(stat_ref.st_size), static_cast<int>(stat_ref.st_dev),
static_cast<jlong>(stat_ref.st_ino));
}
static void SetIntField(JNIEnv *env,
const jclass &clazz,
const jobject &object,
const char *name,
int val) {
jfieldID fid = env->GetFieldID(clazz, name, "I");
CHECK(fid != NULL);
env->SetIntField(object, fid, val);
}
extern "C" JNIEXPORT void JNICALL
Java_com_google_devtools_build_lib_unix_ErrnoFileStatus_00024ErrnoConstants_initErrnoConstants( // NOLINT
JNIEnv *env, jobject errno_constants) {
jclass clazz = env->GetObjectClass(errno_constants);
SetIntField(env, clazz, errno_constants, "ENOENT", ENOENT);
SetIntField(env, clazz, errno_constants, "EACCES", EACCES);
SetIntField(env, clazz, errno_constants, "ELOOP", ELOOP);
SetIntField(env, clazz, errno_constants, "ENOTDIR", ENOTDIR);
SetIntField(env, clazz, errno_constants, "ENAMETOOLONG", ENAMETOOLONG);
}
static jobject StatCommon(JNIEnv *env,
jstring path,
int (*stat_function)(const char *, portable_stat_struct *),
bool should_throw) {
portable_stat_struct statbuf;
const char *path_chars = GetStringLatin1Chars(env, path);
int r;
int saved_errno = 0;
while ((r = stat_function(path_chars, &statbuf)) == -1 && errno == EINTR) { }
if (r == -1) {
// Save errno immediately, before we do any other syscalls
saved_errno = errno;
// EACCES ENOENT ENOTDIR ELOOP -> IOException
// ENAMETOOLONGEFAULT -> RuntimeException
// ENOMEM -> OutOfMemoryError
if (PostRuntimeException(env, saved_errno, path_chars)) {
::ReleaseStringLatin1Chars(path_chars);
return NULL;
} else if (should_throw) {
::PostFileException(env, saved_errno, path_chars);
::ReleaseStringLatin1Chars(path_chars);
return NULL;
}
}
::ReleaseStringLatin1Chars(path_chars);
return should_throw
? NewFileStatus(env, statbuf)
: NewErrnoFileStatus(env, saved_errno, statbuf);
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: stat
* Signature: (Ljava/lang/String;)Lcom/google/devtools/build/lib/unix/FileStatus;
* Throws: java.io.IOException
*/
extern "C" JNIEXPORT jobject JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_stat(JNIEnv *env,
jclass clazz,
jstring path) {
return ::StatCommon(env, path, portable_stat, true);
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: lstat
* Signature: (Ljava/lang/String;)Lcom/google/devtools/build/lib/unix/FileStatus;
* Throws: java.io.IOException
*/
extern "C" JNIEXPORT jobject JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_lstat(JNIEnv *env,
jclass clazz,
jstring path) {
return ::StatCommon(env, path, portable_lstat, true);
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: statNullable
* Signature: (Ljava/lang/String;)Lcom/google/devtools/build/lib/unix/FileStatus;
*/
extern "C" JNIEXPORT jobject JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_errnoStat(JNIEnv *env,
jclass clazz,
jstring path) {
return ::StatCommon(env, path, portable_stat, false);
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: lstatNullable
* Signature: (Ljava/lang/String;)Lcom/google/devtools/build/lib/unix/FileStatus;
*/
extern "C" JNIEXPORT jobject JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_errnoLstat(JNIEnv *env,
jclass clazz,
jstring path) {
return ::StatCommon(env, path, portable_lstat, false);
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: utime
* Signature: (Ljava/lang/String;ZII)V
* Throws: java.io.IOException
*/
extern "C" JNIEXPORT void JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_utime(JNIEnv *env,
jclass clazz,
jstring path,
jboolean now,
jint modtime) {
const char *path_chars = GetStringLatin1Chars(env, path);
#ifdef __linux
struct timespec spec[2] = {{0, UTIME_OMIT}, {modtime, now ? UTIME_NOW : 0}};
if (::utimensat(AT_FDCWD, path_chars, spec, 0) == -1) {
::PostFileException(env, errno, path_chars);
}
#else
struct utimbuf buf = { modtime, modtime };
struct utimbuf *bufptr = now ? NULL : &buf;
if (::utime(path_chars, bufptr) == -1) {
// EACCES ENOENT EMULTIHOP ELOOP EINTR
// ENOTDIR ENOLINK EPERM EROFS -> IOException
// EFAULT ENAMETOOLONG -> RuntimeException
::PostFileException(env, errno, path_chars);
}
#endif
ReleaseStringLatin1Chars(path_chars);
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: umask
* Signature: (I)I
*/
extern "C" JNIEXPORT jint JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_umask(JNIEnv *env,
jclass clazz,
jint new_umask) {
return ::umask(new_umask);
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: mkdir
* Signature: (Ljava/lang/String;I)Z
* Throws: java.io.IOException
*/
extern "C" JNIEXPORT jboolean JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_mkdir(JNIEnv *env,
jclass clazz,
jstring path,
jint mode) {
const char *path_chars = GetStringLatin1Chars(env, path);
jboolean result = true;
if (::mkdir(path_chars, mode) == -1) {
// EACCES ENOENT ELOOP
// ENOSPC ENOTDIR EPERM EROFS -> IOException
// EFAULT ENAMETOOLONG -> RuntimeException
// ENOMEM -> OutOfMemoryError
// EEXIST -> return false
if (errno == EEXIST) {
result = false;
} else {
::PostFileException(env, errno, path_chars);
}
}
ReleaseStringLatin1Chars(path_chars);
return result;
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: mkdirs
* Signature: (Ljava/lang/String;I)V
* Throws: java.io.IOException
*/
extern "C" JNIEXPORT void JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_mkdirs(JNIEnv *env,
jclass clazz,
jstring path,
int mode) {
char *path_chars = GetStringLatin1Chars(env, path);
portable_stat_struct statbuf;
int len;
char *p;
// First, check if the directory already exists and early-out.
if (portable_stat(path_chars, &statbuf) == 0) {
if (!S_ISDIR(statbuf.st_mode)) {
// Exists but is not a directory.
::PostFileException(env, ENOTDIR, path_chars);
}
goto cleanup;
} else if (errno != ENOENT) {
::PostFileException(env, errno, path_chars);
goto cleanup;
}
// Find the first directory that already exists and leave a pointer just past
// it.
len = strlen(path_chars);
p = path_chars + len - 1;
for (; p > path_chars; --p) {
if (*p == '/') {
*p = 0;
int res = portable_stat(path_chars, &statbuf);
*p = '/';
if (res == 0) {
// Exists and must be a directory, or the initial stat would have failed
// with ENOTDIR.
break;
} else if (errno != ENOENT) {
::PostFileException(env, errno, path_chars);
goto cleanup;
}
}
}
// p now points at the '/' after the last directory that exists.
// Successively create each directory
for (const char *end = path_chars + len; p < end; ++p) {
if (*p == '/') {
*p = 0;
int res = ::mkdir(path_chars, mode);
*p = '/';
// EEXIST is fine, just means we're racing to create the directory.
// Note that somebody could have raced to create a file here, but that
// will get handled by a ENOTDIR by a subsequent mkdir call.
if (res != 0 && errno != EEXIST) {
::PostFileException(env, errno, path_chars);
goto cleanup;
}
}
}
if (::mkdir(path_chars, mode) != 0) {
if (errno != EEXIST) {
::PostFileException(env, errno, path_chars);
goto cleanup;
}
if (portable_stat(path_chars, &statbuf) != 0) {
::PostFileException(env, errno, path_chars);
goto cleanup;
}
if (!S_ISDIR(statbuf.st_mode)) {
// Exists but is not a directory.
::PostFileException(env, ENOTDIR, path_chars);
goto cleanup;
}
}
cleanup:
ReleaseStringLatin1Chars(path_chars);
}
static jobject NewDirents(JNIEnv *env,
jobjectArray names,
jbyteArray types) {
// See http://java.sun.com/docs/books/jni/html/fldmeth.html#26855
static jclass dirents_class = NULL;
if (dirents_class == NULL) { // note: harmless race condition
jclass local = env->FindClass("com/google/devtools/build/lib/unix/NativePosixFiles$Dirents");
CHECK(local != NULL);
dirents_class = static_cast<jclass>(env->NewGlobalRef(local));
}
static jmethodID ctor = NULL;
if (ctor == NULL) { // note: harmless race condition
ctor = env->GetMethodID(dirents_class, "<init>", "([Ljava/lang/String;[B)V");
CHECK(ctor != NULL);
}
return env->NewObject(dirents_class, ctor, names, types);
}
static char GetDirentType(struct dirent *entry,
int dirfd,
bool follow_symlinks) {
switch (entry->d_type) {
case DT_REG:
return 'f';
case DT_DIR:
return 'd';
case DT_LNK:
if (!follow_symlinks) {
return 's';
}
FALLTHROUGH_INTENDED;
case DT_UNKNOWN:
portable_stat_struct statbuf;
if (portable_fstatat(dirfd, entry->d_name, &statbuf, 0) == 0) {
if (S_ISREG(statbuf.st_mode)) return 'f';
if (S_ISDIR(statbuf.st_mode)) return 'd';
}
// stat failed or returned something weird; fall through
FALLTHROUGH_INTENDED;
default:
return '?';
}
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: readdir
* Signature: (Ljava/lang/String;Z)Lcom/google/devtools/build/lib/unix/Dirents;
* Throws: java.io.IOException
*/
extern "C" JNIEXPORT jobject JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_readdir(JNIEnv *env,
jclass clazz,
jstring path,
jchar read_types) {
const char *path_chars = GetStringLatin1Chars(env, path);
DIR *dirh;
while ((dirh = ::opendir(path_chars)) == NULL && errno == EINTR) { }
if (dirh == NULL) {
// EACCES EMFILE ENFILE ENOENT ENOTDIR -> IOException
// ENOMEM -> OutOfMemoryError
::PostFileException(env, errno, path_chars);
}
ReleaseStringLatin1Chars(path_chars);
if (dirh == NULL) {
return NULL;
}
int fd = dirfd(dirh);
std::vector<std::string> entries;
std::vector<jbyte> types;
for (;;) {
// Clear errno beforehand. Because readdir() is not required to clear it at
// EOF, this is the only way to reliably distinguish EOF from error.
errno = 0;
struct dirent *entry = ::readdir(dirh);
if (entry == NULL) {
if (errno == 0) break; // EOF
// It is unclear whether an error can also skip some records.
// That does not appear to happen with glibc, at least.
if (errno == EINTR) continue; // interrupted by a signal
if (errno == EIO) continue; // glibc returns this on transient errors
// Otherwise, this is a real error we should report.
::PostFileException(env, errno, path_chars);
::closedir(dirh);
return NULL;
}
// Omit . and .. from results.
if (entry->d_name[0] == '.') {
if (entry->d_name[1] == '\0') continue;
if (entry->d_name[1] == '.' && entry->d_name[2] == '\0') continue;
}
entries.push_back(entry->d_name);
if (read_types != 'n') {
types.push_back(GetDirentType(entry, fd, read_types == 'f'));
}
}
if (::closedir(dirh) < 0 && errno != EINTR) {
::PostFileException(env, errno, path_chars);
return NULL;
}
size_t len = entries.size();
jclass jlStringClass = env->GetObjectClass(path);
jobjectArray names_obj = env->NewObjectArray(len, jlStringClass, NULL);
if (names_obj == NULL && env->ExceptionOccurred()) {
return NULL; // async exception!
}
for (size_t ii = 0; ii < len; ++ii) {
jstring s = NewStringLatin1(env, entries[ii].c_str());
if (s == NULL && env->ExceptionOccurred()) {
return NULL; // async exception!
}
env->SetObjectArrayElement(names_obj, ii, s);
}
jbyteArray types_obj = NULL;
if (read_types != 'n') {
CHECK(len == types.size());
types_obj = env->NewByteArray(len);
CHECK(types_obj);
if (len > 0) {
env->SetByteArrayRegion(types_obj, 0, len, &types[0]);
}
}
return NewDirents(env, names_obj, types_obj);
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: rename
* Signature: (Ljava/lang/String;Ljava/lang/String;)V
* Throws: java.io.IOException
*/
extern "C" JNIEXPORT void JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_rename(JNIEnv *env,
jclass clazz,
jstring oldpath,
jstring newpath) {
const char *oldpath_chars = GetStringLatin1Chars(env, oldpath);
const char *newpath_chars = GetStringLatin1Chars(env, newpath);
if (::rename(oldpath_chars, newpath_chars) == -1) {
// EISDIR EXDEV ENOTEMPTY EEXIST EBUSY
// EINVAL EMLINK ENOTDIR EACCES EPERM
// ENOENT EROFS ELOOP ENOSPC -> IOException
// EFAULT ENAMETOOLONG -> RuntimeException
// ENOMEM -> OutOfMemoryError
std::string filename(std::string(oldpath_chars) + " -> " + newpath_chars);
::PostFileException(env, errno, filename.c_str());
}
ReleaseStringLatin1Chars(oldpath_chars);
ReleaseStringLatin1Chars(newpath_chars);
}
static bool delete_common(JNIEnv *env,
jstring path,
int (*delete_function)(const char *),
bool (*error_function)(int)) {
const char *path_chars = GetStringLatin1Chars(env, path);
if (path_chars == NULL) {
return false;
}
bool ok = delete_function(path_chars) != -1;
if (!ok) {
if (!error_function(errno)) {
::PostFileException(env, errno, path_chars);
}
}
ReleaseStringLatin1Chars(path_chars);
return ok;
}
static bool unlink_err(int err) { return err == ENOENT; }
static bool remove_err(int err) { return err == ENOENT || err == ENOTDIR; }
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: unlink
* Signature: (Ljava/lang/String;)V
* Throws: java.io.IOException
*/
extern "C" JNIEXPORT bool JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_unlink(JNIEnv *env,
jclass clazz,
jstring path) {
return ::delete_common(env, path, ::unlink, ::unlink_err);
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: remove
* Signature: (Ljava/lang/String;)V
* Throws: java.io.IOException
*/
extern "C" JNIEXPORT bool JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_remove(JNIEnv *env,
jclass clazz,
jstring path) {
return ::delete_common(env, path, ::remove, ::remove_err);
}
/*
* Class: com.google.devtools.build.lib.unix.NativePosixFiles
* Method: mkfifo
* Signature: (Ljava/lang/String;I)V
* Throws: java.io.IOException
*/
extern "C" JNIEXPORT void JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_mkfifo(JNIEnv *env,
jclass clazz,
jstring path,
jint mode) {
const char *path_chars = GetStringLatin1Chars(env, path);
if (mkfifo(path_chars, mode) == -1) {
::PostFileException(env, errno, path_chars);
}
ReleaseStringLatin1Chars(path_chars);
}
////////////////////////////////////////////////////////////////////////
// Linux extended file attributes
typedef ssize_t getxattr_func(const char *path, const char *name,
void *value, size_t size, bool *attr_not_found);
static jbyteArray getxattr_common(JNIEnv *env,
jstring path,
jstring name,
getxattr_func getxattr) {
const char *path_chars = GetStringLatin1Chars(env, path);
const char *name_chars = GetStringLatin1Chars(env, name);
// TODO(bazel-team): on ERANGE, try again with larger buffer.
jbyte value[4096];
jbyteArray result = NULL;
bool attr_not_found = false;
ssize_t size = getxattr(path_chars, name_chars, value, arraysize(value),
&attr_not_found);
if (size == -1) {
if (!attr_not_found) {
::PostFileException(env, errno, path_chars);
}
} else {
result = env->NewByteArray(size);
env->SetByteArrayRegion(result, 0, size, value);
}
ReleaseStringLatin1Chars(path_chars);
ReleaseStringLatin1Chars(name_chars);
return result;
}
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_getxattr(JNIEnv *env,
jclass clazz,
jstring path,
jstring name) {
return ::getxattr_common(env, path, name, ::portable_getxattr);
}
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_lgetxattr(JNIEnv *env,
jclass clazz,
jstring path,
jstring name) {
return ::getxattr_common(env, path, name, ::portable_lgetxattr);
}
// Computes MD5 digest of "file", writes result in "result", which
// must be of length Md5Digest::kDigestLength. Returns zero on success, or
// -1 (and sets errno) otherwise.
static int md5sumAsBytes(const char *file,
jbyte result[Md5Digest::kDigestLength]) {
Md5Digest digest;
// OPT: Using a 32k buffer would give marginally better performance,
// but what is the stack size here?
jbyte buf[8192];
int fd;
while ((fd = open(file, O_RDONLY)) == -1 && errno == EINTR) { }
if (fd == -1) {
return -1;
}
for (ssize_t len = read(fd, buf, arraysize(buf));
len != 0;
len = read(fd, buf, arraysize(buf))) {
if (len == -1) {
if (errno == EINTR) {
continue;
} else {
int read_errno = errno;
close(fd); // prefer read() errors over close().
errno = read_errno;
return -1;
}
}
digest.Update(buf, len);
}
if (close(fd) < 0 && errno != EINTR) {
return -1;
}
digest.Finish(reinterpret_cast<unsigned char*>(result));
return 0;
}
extern "C" JNIEXPORT jbyteArray JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixFiles_md5sumAsBytes(
JNIEnv *env, jclass clazz, jstring path) {
const char *path_chars = GetStringLatin1Chars(env, path);
jbyte value[Md5Digest::kDigestLength];
jbyteArray result = NULL;
if (md5sumAsBytes(path_chars, value) == 0) {
result = env->NewByteArray(Md5Digest::kDigestLength);
env->SetByteArrayRegion(result, 0, Md5Digest::kDigestLength, value);
} else {
::PostFileException(env, errno, path_chars);
}
ReleaseStringLatin1Chars(path_chars);
return result;
}
extern "C" JNIEXPORT jlong JNICALL
Java_com_google_devtools_build_lib_unix_NativePosixSystem_sysctlbynameGetLong(
JNIEnv *env, jclass clazz, jstring name) {
const char *name_chars = GetStringLatin1Chars(env, name);
long r;
size_t len = sizeof(r);
if (portable_sysctlbyname(name_chars, &r, &len) == -1) {
::PostSystemException(env, errno, "sysctlbyname", name_chars);
}
ReleaseStringLatin1Chars(name_chars);
return (jlong)r;
}