// 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 = reinterpret_cast(malloc(len * sizeof(jchar))); if (str1 == 0) { ::PostException(env, ENOMEM, "Out of memory in NewStringLatin1"); return NULL; } } 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) { free(str1); } return result; } /** * 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); const jchar *str = env->GetStringCritical(jstr, NULL); if (str == NULL) { return NULL; } char *result = reinterpret_cast(malloc(len + 1)); if (result == NULL) { env->ReleaseStringCritical(jstr, str); ::PostException(env, ENOMEM, "Out of memory in GetStringLatin1Chars"); return NULL; } 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) { if (s != NULL) { free(const_cast(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(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(env->NewGlobalRef(local)); } static jmethodID method = NULL; if (method == NULL) { // note: harmless race condition method = env->GetMethodID(file_status_class, "", "(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(stat_ref.st_size), static_cast(stat_ref.st_dev), static_cast(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(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, "", "(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, "", "(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(stat_ref.st_size), static_cast(stat_ref.st_dev), static_cast(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(env->NewGlobalRef(local)); } static jmethodID ctor = NULL; if (ctor == NULL) { // note: harmless race condition ctor = env->GetMethodID(dirents_class, "", "([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 entries; std::vector 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(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; }