| // 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 <dirent.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <ifaddrs.h> |
| #include <jni.h> |
| #include <limits.h> |
| #include <netdb.h> |
| #include <netinet/in.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <sys/resource.h> |
| #include <sys/socket.h> |
| #include <sys/stat.h> |
| #include <sys/syscall.h> |
| #include <sys/time.h> |
| #include <sys/types.h> |
| #include <unistd.h> |
| #include <utime.h> |
| |
| // Linting disabled for this line because for google code we could use |
| // absl::Mutex but we cannot yet because Bazel doesn't depend on absl. |
| #include <mutex> // NOLINT |
| #include <string> |
| #include <vector> |
| |
| #include "src/main/cpp/util/logging.h" |
| #include "src/main/cpp/util/port.h" |
| #include "src/main/native/latin1_jni_path.h" |
| #include "src/main/native/macros.h" |
| |
| #if defined(O_DIRECTORY) |
| #define PORTABLE_O_DIRECTORY O_DIRECTORY |
| #else |
| #define PORTABLE_O_DIRECTORY 0 |
| #endif |
| |
| namespace blaze_jni { |
| |
| struct DIROrError { |
| DIR *dir; |
| int error; |
| }; |
| |
| static void PostException(JNIEnv *env, const char *exception_classname, |
| const std::string &message) { |
| jclass exception_class = env->FindClass(exception_classname); |
| bool success = false; |
| if (exception_class != nullptr) { |
| success = env->ThrowNew(exception_class, message.c_str()) == 0; |
| } |
| if (!success) { |
| BAZEL_LOG(FATAL) << "Failure to throw java error: " << message.c_str(); |
| } |
| } |
| |
| // 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 (unlikely; perhaps from or via FUSE?) |
| 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 EINVAL: // Invalid argument |
| exception_classname = |
| "com/google/devtools/build/lib/unix/InvalidArgumentIOException"; |
| break; |
| case ELOOP: // Too many symbolic links encountered |
| exception_classname = |
| "com/google/devtools/build/lib/vfs/FileSymlinkLoopException"; |
| break; |
| case EBADF: // Bad file number or descriptor already closed. |
| case ENAMETOOLONG: // File name too long |
| case ENODATA: // No data available |
| #if defined(EMULTIHOP) |
| case EMULTIHOP: // Multihop attempted |
| #endif |
| 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 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"; |
| } |
| PostException(env, exception_classname, |
| message + " (" + ErrorMessage(error_number) + ")"); |
| } |
| |
| static void PostAssertionError(JNIEnv *env, const std::string& message) { |
| PostException(env, "java/lang/AssertionError", message); |
| } |
| |
| // 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 = nullptr; |
| } |
| |
| if (exception_classname == nullptr) { |
| return false; |
| } |
| |
| jclass exception_class = env->FindClass(exception_classname); |
| if (exception_class != nullptr) { |
| std::string message(file_path); |
| message += " ("; |
| message += ErrorMessage(error_number); |
| message += ")"; |
| env->ThrowNew(exception_class, message.c_str()); |
| return true; |
| } else { |
| BAZEL_LOG(FATAL) << "Unable to find exception_class: " |
| << exception_classname; |
| return false; |
| } |
| } |
| |
| static JavaVM *GetJavaVM(JNIEnv *env) { |
| static JavaVM *java_vm = nullptr; |
| static std::mutex java_vm_mtx; |
| std::lock_guard<std::mutex> lock(java_vm_mtx); |
| if (env != nullptr) { |
| JavaVM *env_java_vm; |
| jint value = env->GetJavaVM(&env_java_vm); |
| if (value != 0) { |
| return nullptr; |
| } |
| if (java_vm == nullptr) { |
| java_vm = env_java_vm; |
| } else if (java_vm != env_java_vm) { |
| return nullptr; |
| } |
| } |
| return java_vm; |
| } |
| |
| static void PerformIntegerValueCallback(jobject object, const char *callback, |
| int value) { |
| JavaVM *java_vm = GetJavaVM(nullptr); |
| JNIEnv *java_env; |
| int status = java_vm->GetEnv((void **)&java_env, JNI_VERSION_1_8); |
| bool attach_current_thread = false; |
| if (status == JNI_EDETACHED) { |
| attach_current_thread = true; |
| } else { |
| BAZEL_CHECK_EQ(status, JNI_OK); |
| } |
| if (attach_current_thread) { |
| BAZEL_CHECK_EQ(java_vm->AttachCurrentThread((void **)&java_env, nullptr), |
| 0); |
| } |
| jclass clazz = java_env->GetObjectClass(object); |
| BAZEL_CHECK_NE(clazz, nullptr); |
| jmethodID method_id = java_env->GetMethodID(clazz, callback, "(I)V"); |
| BAZEL_CHECK_NE(method_id, nullptr); |
| java_env->CallVoidMethod(object, method_id, value); |
| |
| if (attach_current_thread) { |
| BAZEL_CHECK_EQ(java_vm->DetachCurrentThread(), JNI_OK); |
| } |
| } |
| |
| // 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 = nullptr; |
| if (readlink(path_chars, target, arraysize(target)) == -1) { |
| PostException(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) { |
| PostException(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) { |
| PostException(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); |
| } |
| |
| namespace { |
| static jclass file_status_class = nullptr; |
| static jclass errno_file_status_class = nullptr; |
| static jmethodID file_status_class_ctor = nullptr; |
| static jmethodID errno_file_status_class_no_error_ctor = nullptr; |
| static jmethodID errno_file_status_class_errorno_ctor = nullptr; |
| static jclass dirents_class = nullptr; |
| static jmethodID dirents_ctor = nullptr; |
| |
| static jclass makeStaticClass(JNIEnv *env, const char *name) { |
| jclass lookup_result = env->FindClass(name); |
| BAZEL_CHECK_NE(lookup_result, nullptr); |
| return static_cast<jclass>(env->NewGlobalRef(lookup_result)); |
| } |
| |
| static jmethodID getConstructorID(JNIEnv *env, jclass clazz, |
| const char *parameters) { |
| jmethodID method = env->GetMethodID(clazz, "<init>", parameters); |
| BAZEL_CHECK_NE(method, nullptr); |
| return method; |
| } |
| |
| static jobject NewFileStatus(JNIEnv *env, |
| const portable_stat_struct &stat_ref) { |
| return env->NewObject( |
| file_status_class, file_status_class_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 jobject NewErrnoFileStatus(JNIEnv *env, |
| int saved_errno, |
| const portable_stat_struct &stat_ref) { |
| if (saved_errno != 0) { |
| return env->NewObject(errno_file_status_class, |
| errno_file_status_class_errorno_ctor, saved_errno); |
| } |
| return env->NewObject( |
| errno_file_status_class, 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"); |
| BAZEL_CHECK_NE(fid, nullptr); |
| env->SetIntField(object, fid, val); |
| } |
| |
| // RAII class for jstring. |
| class JStringLatin1Holder { |
| const char *const chars; |
| |
| public: |
| JStringLatin1Holder(JNIEnv *env, jstring string) |
| : chars(GetStringLatin1Chars(env, string)) {} |
| |
| ~JStringLatin1Holder() { ReleaseStringLatin1Chars(chars); } |
| |
| operator const char *() const { return chars; } |
| |
| operator std::string() const { return chars; } |
| }; |
| |
| } // namespace |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_unix_NativePosixFiles_initJNIClasses( |
| JNIEnv *env, jclass clazz) { |
| file_status_class = |
| makeStaticClass(env, "com/google/devtools/build/lib/unix/FileStatus"); |
| errno_file_status_class = makeStaticClass( |
| env, "com/google/devtools/build/lib/unix/ErrnoFileStatus"); |
| file_status_class_ctor = |
| getConstructorID(env, file_status_class, "(IIIIIIIJIJ)V"); |
| errno_file_status_class_no_error_ctor = |
| getConstructorID(env, errno_file_status_class, "(IIIIIIIJIJ)V"); |
| errno_file_status_class_errorno_ctor = |
| getConstructorID(env, errno_file_status_class, "(I)V"); |
| dirents_class = makeStaticClass( |
| env, "com/google/devtools/build/lib/unix/NativePosixFiles$Dirents"); |
| dirents_ctor = |
| getConstructorID(env, dirents_class, "([Ljava/lang/String;[B)V"); |
| } |
| |
| 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, "errnoENOENT", ENOENT); |
| SetIntField(env, clazz, errno_constants, "errnoEACCES", EACCES); |
| SetIntField(env, clazz, errno_constants, "errnoELOOP", ELOOP); |
| SetIntField(env, clazz, errno_constants, "errnoENOTDIR", ENOTDIR); |
| SetIntField(env, clazz, errno_constants, "errnoENAMETOOLONG", ENAMETOOLONG); |
| SetIntField(env, clazz, errno_constants, "errnoENODATA", ENODATA); |
| } |
| |
| namespace { |
| 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 nullptr; |
| } else if (should_throw) { |
| PostException(env, saved_errno, path_chars); |
| ReleaseStringLatin1Chars(path_chars); |
| return nullptr; |
| } |
| } |
| ReleaseStringLatin1Chars(path_chars); |
| |
| return should_throw |
| ? NewFileStatus(env, statbuf) |
| : NewErrnoFileStatus(env, saved_errno, statbuf); |
| } |
| } // namespace |
| |
| /* |
| * 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) { |
| PostException(env, errno, path_chars); |
| } |
| #else |
| struct utimbuf buf = { modtime, modtime }; |
| struct utimbuf *bufptr = now ? nullptr : &buf; |
| if (::utime(path_chars, bufptr) == -1) { |
| // EACCES ENOENT EMULTIHOP ELOOP EINTR |
| // ENOTDIR ENOLINK EPERM EROFS -> IOException |
| // EFAULT ENAMETOOLONG -> RuntimeException |
| PostException(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 { |
| PostException(env, errno, path_chars); |
| } |
| } |
| ReleaseStringLatin1Chars(path_chars); |
| return result; |
| } |
| |
| /* |
| * Class: com.google.devtools.build.lib.unix.NativePosixFiles |
| * Method: mkdirWritable |
| * Signature: (Ljava/lang/String;I)Z |
| * Throws: java.io.IOException |
| */ |
| extern "C" JNIEXPORT jboolean JNICALL |
| Java_com_google_devtools_build_lib_unix_NativePosixFiles_mkdirWritable( |
| JNIEnv *env, jclass clazz, jstring path) { |
| JStringLatin1Holder path_chars(env, path); |
| |
| portable_stat_struct statbuf; |
| int r; |
| do { |
| r = portable_lstat(path_chars, &statbuf); |
| } while (r != 0 && errno == EINTR); |
| |
| if (r != 0) { |
| if (errno != ENOENT) { |
| PostException(env, errno, path_chars); |
| return false; |
| } |
| // directory does not exist. |
| if (::mkdir(path_chars, 0755) == -1) { |
| PostException(env, errno, path_chars); |
| } |
| return true; |
| } |
| // path already exists |
| if (!S_ISDIR(statbuf.st_mode)) { |
| PostException(env, ENOTDIR, path_chars); |
| return false; |
| } |
| // Make sure the mode is correct. |
| if ((statbuf.st_mode & 0755) != 0755 && ::chmod(path_chars, 0755) == -1) { |
| PostException(env, errno, path_chars); |
| } |
| return false; |
| } |
| |
| /* |
| * 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. |
| PostException(env, ENOTDIR, path_chars); |
| } |
| goto cleanup; |
| } else if (errno != ENOENT) { |
| PostException(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) { |
| PostException(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) { |
| PostException(env, errno, path_chars); |
| goto cleanup; |
| } |
| } |
| } |
| if (::mkdir(path_chars, mode) != 0) { |
| if (errno != EEXIST) { |
| PostException(env, errno, path_chars); |
| goto cleanup; |
| } |
| if (portable_stat(path_chars, &statbuf) != 0) { |
| PostException(env, errno, path_chars); |
| goto cleanup; |
| } |
| if (!S_ISDIR(statbuf.st_mode)) { |
| // Exists but is not a directory. |
| PostException(env, ENOTDIR, path_chars); |
| goto cleanup; |
| } |
| } |
| cleanup: |
| ReleaseStringLatin1Chars(path_chars); |
| } |
| |
| namespace { |
| static jobject NewDirents(JNIEnv *env, |
| jobjectArray names, |
| jbyteArray types) { |
| return env->NewObject(dirents_class, dirents_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 '?'; |
| } |
| } |
| } // namespace |
| |
| /* |
| * 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)) == nullptr && errno == EINTR) { |
| } |
| if (dirh == nullptr) { |
| // EACCES EMFILE ENFILE ENOENT ENOTDIR -> IOException |
| // ENOMEM -> OutOfMemoryError |
| PostException(env, errno, path_chars); |
| } |
| ReleaseStringLatin1Chars(path_chars); |
| if (dirh == nullptr) { |
| return nullptr; |
| } |
| 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 == nullptr) { |
| 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. |
| PostException(env, errno, path_chars); |
| ::closedir(dirh); |
| return nullptr; |
| } |
| // 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) { |
| PostException(env, errno, path_chars); |
| return nullptr; |
| } |
| |
| size_t len = entries.size(); |
| jclass jlStringClass = env->GetObjectClass(path); |
| jobjectArray names_obj = env->NewObjectArray(len, jlStringClass, nullptr); |
| if (names_obj == nullptr && env->ExceptionOccurred()) { |
| return nullptr; // async exception! |
| } |
| |
| for (size_t ii = 0; ii < len; ++ii) { |
| jstring s = NewStringLatin1(env, entries[ii].c_str()); |
| if (s == nullptr && env->ExceptionOccurred()) { |
| return nullptr; // async exception! |
| } |
| env->SetObjectArrayElement(names_obj, ii, s); |
| } |
| |
| jbyteArray types_obj = nullptr; |
| if (read_types != 'n') { |
| BAZEL_CHECK_EQ(len, types.size()); |
| types_obj = env->NewByteArray(len); |
| BAZEL_CHECK_NE(types_obj, nullptr); |
| 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); |
| PostException(env, errno, filename); |
| } |
| ReleaseStringLatin1Chars(oldpath_chars); |
| ReleaseStringLatin1Chars(newpath_chars); |
| } |
| |
| /* |
| * 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) { |
| const char *path_chars = GetStringLatin1Chars(env, path); |
| if (path_chars == nullptr) { |
| return false; |
| } |
| bool ok = remove(path_chars) != -1; |
| if (!ok) { |
| if (errno != ENOENT && errno != ENOTDIR) { |
| PostException(env, errno, path_chars); |
| } |
| } |
| ReleaseStringLatin1Chars(path_chars); |
| return ok; |
| } |
| |
| /* |
| * 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) { |
| PostException(env, errno, path_chars); |
| } |
| ReleaseStringLatin1Chars(path_chars); |
| } |
| |
| namespace { |
| // Posts an exception generated by the DeleteTreesBelow algorithm and its helper |
| // functions. |
| // |
| // This is just a convenience wrapper over PostException to format the |
| // path that caused an error only when necessary, as we keep that path tokenized |
| // throughout the deletion process. |
| // |
| // env is the JNI environment in which to post the exception. error and function |
| // capture the errno value and the name of the system function that triggered |
| // it. The faulty path is specified by all the components of dir_path and the |
| // optional entry subcomponent, which may be NULL. |
| static void PostDeleteTreesBelowException( |
| JNIEnv* env, int error, const char* function, |
| const std::vector<std::string>& dir_path, const char* entry) { |
| std::vector<std::string>::const_iterator iter = dir_path.begin(); |
| std::string path; |
| if (iter != dir_path.end()) { |
| path = *iter; |
| while (++iter != dir_path.end()) { |
| path += "/"; |
| path += *iter; |
| } |
| if (entry != nullptr) { |
| path += "/"; |
| path += entry; |
| } |
| } else { |
| // When scanning the top-level directory given to DeleteTreesBelow, the |
| // dir_path buffer is still empty but we have the full path in entry. |
| path = entry; |
| } |
| BAZEL_CHECK(!env->ExceptionOccurred()); |
| PostException(env, errno, std::string(function) + " (" + path + ")"); |
| } |
| |
| // Tries to open a directory and, if the first attempt fails, retries after |
| // granting extra permissions to the directory. |
| // |
| // The directory to open is identified by the open descriptor of the parent |
| // directory (dir_fd) and the subpath to resolve within that directory (entry). |
| // dir_path contains the path components that were used when opening dir_fd and |
| // is only used for error reporting purposes. |
| // |
| // Returns a directory handle on success or an errno on error. If the error is |
| // other than ENOENT, posts an exception before returning. |
| static DIROrError ForceOpendir(JNIEnv *env, |
| const std::vector<std::string> &dir_path, |
| const int dir_fd, const char *entry) { |
| static const int flags = O_RDONLY | O_NOFOLLOW | PORTABLE_O_DIRECTORY; |
| int fd = openat(dir_fd, entry, flags); |
| if (fd == -1) { |
| if (errno == ENOENT) { |
| return {nullptr, errno}; |
| } |
| // If dir_fd is a readable but non-executable directory containing entry, we |
| // could have obtained entry by readdir()-ing, but any attempt to open or |
| // stat the entry would fail with EACCESS. In this case, we need to fix the |
| // permissions on dir_fd (which we can do only if it's a "real" file |
| // descriptor, not AT_FDCWD used as the starting point of DeleteTreesBelow |
| // recursion). |
| if (errno == EACCES && dir_fd != AT_FDCWD) { |
| if (fchmod(dir_fd, 0700) == -1) { |
| if (errno != ENOENT) { |
| PostDeleteTreesBelowException(env, errno, "fchmod", dir_path, |
| nullptr); |
| } |
| return {nullptr, errno}; |
| } |
| } |
| if (fchmodat(dir_fd, entry, 0700, 0) == -1) { |
| if (errno != ENOENT) { |
| PostDeleteTreesBelowException(env, errno, "fchmodat", dir_path, entry); |
| } |
| return {nullptr, errno}; |
| } |
| fd = openat(dir_fd, entry, flags); |
| if (fd == -1) { |
| if (errno != ENOENT) { |
| PostDeleteTreesBelowException(env, errno, "opendir", dir_path, entry); |
| } |
| return {nullptr, errno}; |
| } |
| } |
| DIR* dir = fdopendir(fd); |
| if (dir == nullptr) { |
| if (errno != ENOENT) { |
| PostDeleteTreesBelowException(env, errno, "fdopendir", dir_path, entry); |
| } |
| close(fd); |
| return {nullptr, errno}; |
| } |
| return {dir, 0}; |
| } |
| |
| // Tries to delete a file within a directory and, if the first attempt fails, |
| // retries after granting extra write permissions to the directory. |
| // |
| // The file to delete is identified by the open descriptor of the parent |
| // directory (dir_fd) and the subpath to resolve within that directory (entry). |
| // dir_path contains the path components that were used when opening dir_fd and |
| // is only used for error reporting purposes. |
| // |
| // is_dir indicates whether the entry to delete is a directory or not. |
| // |
| // Returns 0 when the file doesn't exist or is successfully deleted. Otherwise, |
| // returns -1 and posts an exception. |
| static int ForceDelete(JNIEnv* env, const std::vector<std::string>& dir_path, |
| const int dir_fd, const char* entry, |
| const bool is_dir) { |
| const int flags = is_dir ? AT_REMOVEDIR : 0; |
| if (unlinkat(dir_fd, entry, flags) == -1) { |
| if (errno == ENOENT) { |
| return 0; |
| } |
| if (fchmod(dir_fd, 0700) == -1) { |
| if (errno == ENOENT) { |
| return 0; |
| } |
| PostDeleteTreesBelowException(env, errno, "fchmod", dir_path, nullptr); |
| return -1; |
| } |
| if (unlinkat(dir_fd, entry, flags) == -1) { |
| if (errno == ENOENT) { |
| return 0; |
| } |
| PostDeleteTreesBelowException(env, errno, "unlinkat", dir_path, entry); |
| return -1; |
| } |
| } |
| return 0; |
| } |
| |
| // Returns true if the given directory entry represents a subdirectory of dir. |
| // |
| // The file to check is identified by the open descriptor of the parent |
| // directory (dir_fd) and the directory entry within that directory (de). |
| // dir_path contains the path components that were used when opening dir_fd and |
| // is only used for error reporting purposes. |
| // |
| // This function prefers to extract the type information from the directory |
| // entry itself if available. If not available, issues a stat starting from |
| // dir_fd. |
| // |
| // Returns 0 on success and updates is_dir accordingly. Returns -1 on error and |
| // posts an exception. |
| static int IsSubdir(JNIEnv* env, const std::vector<std::string>& dir_path, |
| const int dir_fd, const struct dirent* de, bool* is_dir) { |
| switch (de->d_type) { |
| case DT_DIR: |
| *is_dir = true; |
| return 0; |
| |
| case DT_UNKNOWN: { |
| struct stat st; |
| if (fstatat(dir_fd, de->d_name, &st, AT_SYMLINK_NOFOLLOW) == -1) { |
| if (errno == ENOENT) { |
| *is_dir = false; |
| return 0; |
| } |
| PostDeleteTreesBelowException(env, errno, "fstatat", dir_path, |
| de->d_name); |
| return -1; |
| } |
| *is_dir = st.st_mode & S_IFDIR; |
| return 0; |
| } |
| |
| default: |
| *is_dir = false; |
| return 0; |
| } |
| } |
| |
| // Recursively deletes all trees under the given path. |
| // |
| // The directory to delete is identified by the open descriptor of the parent |
| // directory (dir_fd) and the subpath to resolve within that directory (entry). |
| // dir_path contains the path components that were used when opening dir_fd and |
| // is only used for error reporting purposes. |
| // |
| // dir_path is an in/out parameter updated with the path to the directory being |
| // processed. This avoids the need to construct unnecessary intermediate paths, |
| // as this algorithm works purely on file descriptors: the paths are only used |
| // for error reporting purposes, and therefore are only formatted at that |
| // point. |
| // |
| // Returns 0 on success. Returns -1 on error and posts an exception. |
| static int DeleteTreesBelow(JNIEnv* env, std::vector<std::string>* dir_path, |
| const int dir_fd, const char* entry) { |
| DIROrError dir_or_error = ForceOpendir(env, *dir_path, dir_fd, entry); |
| DIR *dir = dir_or_error.dir; |
| if (dir == nullptr) { |
| if (dir_or_error.error == ENOENT) { |
| return 0; |
| } |
| BAZEL_CHECK_NE(env->ExceptionOccurred(), nullptr); |
| return -1; |
| } |
| |
| dir_path->push_back(entry); |
| // On macOS and some other non-Linux OSes, on some filesystems, readdir(dir) |
| // may return NULL after an entry in dir is deleted even if not all files have |
| // been read yet - see |
| // https://pubs.opengroup.org/onlinepubs/9699919799/functions/readdir.html; |
| // "If a file is removed from or added to the directory after the most recent |
| // call to opendir() or rewinddir(), whether a subsequent call to readdir() |
| // returns an entry for that file is unspecified." We thus read all the names |
| // of dir's entries before deleting. We don't want to simply use fts(3) |
| // because we want to be able to chmod at any point in the directory hierarchy |
| // to retry a filesystem operation after hitting an EACCES. |
| // If in the future we hit any problems here due to the unspecified behavior |
| // of readdir() when a file has been deleted by a different thread we can use |
| // some form of locking to make sure the threads don't try to clean up the |
| // same directory at the same time; or doing it in a loop until the directory |
| // is really empty. |
| std::vector<std::string> dir_files, dir_subdirs; |
| for (;;) { |
| errno = 0; |
| struct dirent* de = readdir(dir); |
| if (de == nullptr) { |
| if (errno != 0 && errno != ENOENT) { |
| PostDeleteTreesBelowException(env, errno, "readdir", *dir_path, |
| nullptr); |
| } |
| break; |
| } |
| |
| if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0) { |
| continue; |
| } |
| |
| bool is_dir; |
| if (IsSubdir(env, *dir_path, dirfd(dir), de, &is_dir) == -1) { |
| BAZEL_CHECK_NE(env->ExceptionOccurred(), nullptr); |
| break; |
| } |
| if (is_dir) { |
| dir_subdirs.push_back(de->d_name); |
| } else { |
| dir_files.push_back(de->d_name); |
| } |
| } |
| if (env->ExceptionOccurred() == nullptr) { |
| for (const auto &file : dir_files) { |
| if (ForceDelete(env, *dir_path, dirfd(dir), file.c_str(), false) == -1) { |
| BAZEL_CHECK_NE(env->ExceptionOccurred(), nullptr); |
| break; |
| } |
| } |
| // DeleteTreesBelow is recursive; don't hold on to file names unnecessarily. |
| dir_files.clear(); |
| } |
| if (env->ExceptionOccurred() == nullptr) { |
| for (const auto &subdir : dir_subdirs) { |
| if (DeleteTreesBelow(env, dir_path, dirfd(dir), subdir.c_str()) == -1) { |
| BAZEL_CHECK_NE(env->ExceptionOccurred(), nullptr); |
| break; |
| } |
| if (ForceDelete(env, *dir_path, dirfd(dir), subdir.c_str(), true) == -1) { |
| BAZEL_CHECK_NE(env->ExceptionOccurred(), nullptr); |
| break; |
| } |
| } |
| } |
| if (closedir(dir) == -1) { |
| // Prefer reporting the error encountered while processing entries, |
| // not the (unlikely) error on close. |
| if (env->ExceptionOccurred() == nullptr) { |
| PostDeleteTreesBelowException(env, errno, "closedir", *dir_path, nullptr); |
| } |
| } |
| dir_path->pop_back(); |
| return env->ExceptionOccurred() == nullptr ? 0 : -1; |
| } |
| } // namespace |
| |
| /* |
| * Class: com.google.devtools.build.lib.unix.NativePosixFiles |
| * Method: deleteTreesBelow |
| * Signature: (Ljava/lang/String;)V |
| * Throws: java.io.IOException |
| */ |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_unix_NativePosixFiles_deleteTreesBelow( |
| JNIEnv *env, jclass clazz, jstring path) { |
| const char *path_chars = GetStringLatin1Chars(env, path); |
| std::vector<std::string> dir_path; |
| if (DeleteTreesBelow(env, &dir_path, AT_FDCWD, path_chars) == -1) { |
| BAZEL_CHECK_NE(env->ExceptionOccurred(), nullptr); |
| } |
| BAZEL_CHECK(dir_path.empty()); |
| ReleaseStringLatin1Chars(path_chars); |
| } |
| |
| //////////////////////////////////////////////////////////////////////// |
| // Linux extended file attributes |
| |
| namespace { |
| 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 = nullptr; |
| 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) { |
| PostException(env, errno, path_chars); |
| } |
| } else { |
| result = env->NewByteArray(size); |
| // Result may be NULL if allocation failed. In that case, we'll return the |
| // NULL and an OOME will be thrown when we are back in Java. |
| if (result != nullptr) { |
| env->SetByteArrayRegion(result, 0, size, value); |
| } |
| } |
| ReleaseStringLatin1Chars(path_chars); |
| ReleaseStringLatin1Chars(name_chars); |
| return result; |
| } |
| } // namespace |
| |
| 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); |
| } |
| |
| extern "C" JNIEXPORT jint JNICALL |
| Java_com_google_devtools_build_lib_unix_NativePosixFiles_openWrite( |
| JNIEnv *env, jclass clazz, jstring path, jboolean append) { |
| const char *path_chars = GetStringLatin1Chars(env, path); |
| int flags = (O_WRONLY | O_CREAT) | (append ? O_APPEND : O_TRUNC); |
| int fd; |
| while ((fd = open(path_chars, flags, 0666)) == -1 && errno == EINTR) { |
| } |
| if (fd == -1) { |
| PostException(env, errno, path_chars); |
| } |
| ReleaseStringLatin1Chars(path_chars); |
| return fd; |
| } |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_unix_NativePosixFiles_close(JNIEnv *env, |
| jclass clazz, |
| jint fd, |
| jobject ingore) { |
| if (close(fd) == -1) { |
| PostException(env, errno, "close"); |
| } |
| } |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_unix_NativePosixFiles_write( |
| JNIEnv *env, jclass clazz, jint fd, jbyteArray data, jint off, jint len) { |
| int data_len = env->GetArrayLength(data); |
| if (off < 0 || len < 0 || off > data_len || data_len - off < len) { |
| jclass oob = env->FindClass("java/lang/IndexOutOfBoundsException"); |
| if (oob != nullptr) { |
| env->ThrowNew(oob, nullptr); |
| } |
| return; |
| } |
| jbyte *buf = static_cast<jbyte *>(malloc(len)); |
| if (buf == nullptr) { |
| PostException(env, ENOMEM, "write"); |
| return; |
| } |
| env->GetByteArrayRegion(data, off, len, buf); |
| // GetByteArrayRegion may raise ArrayIndexOutOfBoundsException if one of the |
| // indexes in the region is not valid. As we obtain the inidices from the |
| // caller, we have to check. |
| if (!env->ExceptionOccurred()) { |
| jbyte *p = buf; |
| while (len > 0) { |
| ssize_t res = write(fd, p, len); |
| if (res == -1) { |
| if (errno != EINTR) { |
| PostException(env, errno, "write"); |
| break; |
| } |
| } else { |
| p += res; |
| len -= res; |
| } |
| } |
| } |
| free(buf); |
| } |
| |
| 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); |
| int64_t r; |
| size_t len = sizeof(r); |
| if (portable_sysctlbyname(name_chars, &r, &len) == -1) { |
| PostException(env, errno, std::string("sysctlbyname(") + name_chars + ")"); |
| } |
| ReleaseStringLatin1Chars(name_chars); |
| return (jlong)r; |
| } |
| |
| /* |
| * Class: com_google_devtools_build_lib_platform_SleepPreventionModule_SleepPrevention |
| * Method: pushDisableSleep |
| * Signature: ()I |
| */ |
| extern "C" JNIEXPORT jint JNICALL |
| Java_com_google_devtools_build_lib_platform_SleepPreventionModule_00024SleepPrevention_pushDisableSleep( |
| JNIEnv *, jclass) { |
| return portable_push_disable_sleep(); |
| } |
| |
| /* |
| * Class: com_google_devtools_build_lib_platform_SleepPreventionModule_SleepPrevention |
| * Method: popDisableSleep |
| * Signature: ()I |
| */ |
| extern "C" JNIEXPORT jint JNICALL |
| Java_com_google_devtools_build_lib_platform_SleepPreventionModule_00024SleepPrevention_popDisableSleep( |
| JNIEnv *, jclass) { |
| return portable_pop_disable_sleep(); |
| } |
| |
| jobject g_suspend_module; |
| |
| /* |
| * Class: Java_com_google_devtools_build_lib_platform_SystemSuspensionModule |
| * Method: registerJNI |
| * Signature: ()V |
| */ |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_platform_SystemSuspensionModule_registerJNI( |
| JNIEnv *env, jobject local_object) { |
| |
| if (g_suspend_module != nullptr) { |
| PostAssertionError(env, |
| "Singleton SystemSuspensionModule already registered"); |
| return; |
| } |
| |
| JavaVM *java_vm = GetJavaVM(env); |
| if (java_vm == nullptr) { |
| PostAssertionError( |
| env, "Unable to get javaVM registering SystemSuspensionModule"); |
| return; |
| } |
| |
| g_suspend_module = env->NewGlobalRef(local_object); |
| if (g_suspend_module == nullptr) { |
| PostAssertionError( |
| env, "Unable to create global ref for SystemSuspensionModule"); |
| return; |
| } |
| portable_start_suspend_monitoring(); |
| } |
| |
| void suspend_callback(SuspensionReason value) { |
| if (g_suspend_module != nullptr) { |
| PerformIntegerValueCallback(g_suspend_module, "suspendCallback", value); |
| } |
| } |
| |
| jobject g_thermal_module; |
| |
| /* |
| * Class: Java_com_google_devtools_build_lib_platform_SystemThermalModule |
| * Method: registerJNI |
| * Signature: ()V |
| */ |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_platform_SystemThermalModule_registerJNI( |
| JNIEnv *env, jobject local_object) { |
| |
| if (g_thermal_module != nullptr) { |
| PostAssertionError(env, |
| "Singleton SystemThermalModule already registered"); |
| return; |
| } |
| |
| JavaVM *java_vm = GetJavaVM(env); |
| if (java_vm == nullptr) { |
| PostAssertionError( |
| env, "Unable to get javaVM registering SystemThermalModule"); |
| return; |
| } |
| |
| g_thermal_module = env->NewGlobalRef(local_object); |
| if (g_thermal_module == nullptr) { |
| PostAssertionError( |
| env, "Unable to create global ref for SystemThermalModule"); |
| return; |
| } |
| portable_start_thermal_monitoring(); |
| } |
| |
| void thermal_callback(int value) { |
| if (g_thermal_module != nullptr) { |
| PerformIntegerValueCallback(g_thermal_module, "thermalCallback", value); |
| } |
| } |
| |
| /* |
| * Class: Java_com_google_devtools_build_lib_platform_SystemThermalModule |
| * Method: thermalLoad |
| * Signature: ()I |
| */ |
| extern "C" JNIEXPORT jint JNICALL |
| Java_com_google_devtools_build_lib_platform_SystemThermalModule_thermalLoad( |
| JNIEnv *env, jclass) { |
| return portable_thermal_load(); |
| } |
| |
| jobject g_system_load_advisory_module; |
| |
| /* |
| * Class: Java_com_google_devtools_build_lib_platform_SystemLoadAdvisoryModule |
| * Method: registerJNI |
| * Signature: ()V |
| */ |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_platform_SystemLoadAdvisoryModule_registerJNI( |
| JNIEnv *env, jobject local_object) { |
| |
| if (g_system_load_advisory_module != nullptr) { |
| PostAssertionError(env, |
| "Singleton SystemLoadAdvisoryModule already registered"); |
| return; |
| } |
| |
| JavaVM *java_vm = GetJavaVM(env); |
| if (java_vm == nullptr) { |
| PostAssertionError( |
| env, "Unable to get javaVM registering SystemLoadAdvisoryModule"); |
| return; |
| } |
| |
| g_system_load_advisory_module = env->NewGlobalRef(local_object); |
| if (g_system_load_advisory_module == nullptr) { |
| PostAssertionError( |
| env, "Unable to create global ref for SystemLoadAdvisoryModule"); |
| return; |
| } |
| portable_start_system_load_advisory_monitoring(); |
| } |
| |
| void system_load_advisory_callback(int value) { |
| if (g_system_load_advisory_module != nullptr) { |
| PerformIntegerValueCallback(g_system_load_advisory_module, |
| "systemLoadAdvisoryCallback", value); |
| } |
| } |
| |
| /* |
| * Class: Java_com_google_devtools_build_lib_platform_SystemLoadAdvisoryModule |
| * Method: systemLoadAdvisory |
| * Signature: ()I |
| */ |
| extern "C" JNIEXPORT jint JNICALL |
| Java_com_google_devtools_build_lib_platform_SystemLoadAdvisoryModule_systemLoadAdvisory( |
| JNIEnv *env, jclass) { |
| return portable_system_load_advisory(); |
| } |
| |
| jobject g_memory_pressure_module; |
| |
| /* |
| * Class: Java_com_google_devtools_build_lib_platform_SystemMemoryPressureMonitor |
| * Method: registerJNI |
| * Signature: ()I |
| */ |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_platform_SystemMemoryPressureMonitor_registerJNI( |
| JNIEnv *env, jobject local_object) { |
| |
| if (g_memory_pressure_module != nullptr) { |
| PostAssertionError( |
| env, "Singleton SystemMemoryPressureModule already registered"); |
| return; |
| } |
| |
| JavaVM *java_vm = GetJavaVM(env); |
| if (java_vm == nullptr) { |
| PostAssertionError( |
| env, "Unable to get javaVM registering SystemMemoryPressureModule"); |
| return; |
| } |
| |
| g_memory_pressure_module = env->NewGlobalRef(local_object); |
| if (g_memory_pressure_module == nullptr) { |
| PostAssertionError( |
| env, "Unable to create global ref for SystemMemoryPressureModule"); |
| return; |
| } |
| portable_start_memory_pressure_monitoring(); |
| } |
| |
| void memory_pressure_callback(MemoryPressureLevel level) { |
| if (g_memory_pressure_module != nullptr) { |
| PerformIntegerValueCallback(g_memory_pressure_module, |
| "memoryPressureCallback", level); |
| } |
| } |
| |
| /* |
| * Class: Java_com_google_devtools_build_lib_platform_SystemMemoryPressureMonitor |
| * Method: systemMemoryPressure |
| * Signature: ()I |
| */ |
| extern "C" JNIEXPORT jint JNICALL |
| Java_com_google_devtools_build_lib_platform_SystemMemoryPressureMonitor_systemMemoryPressure( |
| JNIEnv *env, jclass) { |
| return portable_memory_pressure(); |
| } |
| |
| jobject g_disk_space_module; |
| |
| /* |
| * Class: Java_com_google_devtools_build_lib_platform_SystemDiskSpaceModule |
| * Method: registerJNI |
| * Signature: ()I |
| */ |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_platform_SystemDiskSpaceModule_registerJNI( |
| JNIEnv *env, jobject local_object) { |
| |
| if (g_disk_space_module != nullptr) { |
| PostAssertionError( |
| env, "Singleton SystemDiskSpaceModule already registered"); |
| return; |
| } |
| |
| JavaVM *java_vm = GetJavaVM(env); |
| if (java_vm == nullptr) { |
| PostAssertionError( |
| env, "Unable to get javaVM registering SystemDiskSpaceModule"); |
| return; |
| } |
| |
| g_disk_space_module = env->NewGlobalRef(local_object); |
| if (g_disk_space_module == nullptr) { |
| PostAssertionError( |
| env, "Unable to create global ref for SystemDiskSpaceModule"); |
| return; |
| } |
| portable_start_disk_space_monitoring(); |
| } |
| |
| void disk_space_callback(DiskSpaceLevel level) { |
| if (g_disk_space_module != nullptr) { |
| PerformIntegerValueCallback(g_disk_space_module, "diskSpaceCallback", |
| level); |
| } |
| } |
| |
| jobject g_cpu_speed_module; |
| |
| /* |
| * Class: Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule |
| * Method: registerJNI |
| * Signature: ()I |
| */ |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule_registerJNI( |
| JNIEnv *env, jobject local_object) { |
| |
| if (g_cpu_speed_module != nullptr) { |
| PostAssertionError( |
| env, "Singleton SystemCPUSpeedModule already registered"); |
| return; |
| } |
| |
| JavaVM *java_vm = GetJavaVM(env); |
| if (java_vm == nullptr) { |
| PostAssertionError( |
| env, "Unable to get javaVM registering SystemCPUSpeedModule"); |
| return; |
| } |
| |
| g_cpu_speed_module = env->NewGlobalRef(local_object); |
| if (g_cpu_speed_module == nullptr) { |
| PostAssertionError( |
| env, "Unable to create global ref for SystemCPUSpeedModule"); |
| return; |
| } |
| portable_start_cpu_speed_monitoring(); |
| } |
| |
| void cpu_speed_callback(int speed) { |
| if (g_cpu_speed_module != nullptr) { |
| PerformIntegerValueCallback(g_cpu_speed_module, "cpuSpeedCallback", speed); |
| } |
| } |
| |
| /* |
| * Class: Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule |
| * Method: cpuSpeed |
| * Signature: ()I |
| * |
| * Returns 1-100 to represent CPU speed. Returns -1 in case of error. |
| */ |
| extern "C" JNIEXPORT jint JNICALL |
| Java_com_google_devtools_build_lib_platform_SystemCPUSpeedModule_cpuSpeed( |
| JNIEnv *env, jclass) { |
| return portable_cpu_speed(); |
| } |
| |
| static int convert_ipaddr(struct sockaddr *addr, int family, char *buf, |
| int buf_len) { |
| if (buf_len > 0) { |
| buf[0] = 0; |
| } |
| int addr_len = 0; |
| if (family == AF_INET) { |
| addr_len = sizeof(struct sockaddr_in); |
| } else if (family == AF_INET6) { |
| addr_len = sizeof(struct sockaddr_in6); |
| } |
| if (addr_len != 0) { |
| int err = |
| getnameinfo(addr, addr_len, buf, buf_len, nullptr, 0, NI_NUMERICHOST); |
| if (err != 0) { |
| return err; |
| } |
| } |
| return 0; |
| } |
| |
| extern "C" JNIEXPORT void JNICALL |
| Java_com_google_devtools_build_lib_profiler_SystemNetworkStats_getNetIfAddrsNative( |
| JNIEnv *env, jclass clazz, jobject addrs_list) { |
| ifaddrs *ifaddr; |
| if (getifaddrs(&ifaddr) == -1) { |
| PostException(env, errno, "getifaddrs"); |
| return; |
| } |
| |
| jclass list_class = env->GetObjectClass(addrs_list); |
| jmethodID list_add = |
| env->GetMethodID(list_class, "add", "(Ljava/lang/Object;)Z"); |
| |
| jclass addr_class = env->FindClass( |
| "com/google/devtools/build/lib/profiler/SystemNetworkStats$NetIfAddr"); |
| jmethodID addr_create = env->GetStaticMethodID( |
| addr_class, "create", |
| "(Ljava/lang/String;Lcom/google/devtools/build/lib/profiler/" |
| "SystemNetworkStats$NetIfAddr$Family;Ljava/lang/String;)Lcom/google/" |
| "devtools/build/lib/profiler/SystemNetworkStats$NetIfAddr;"); |
| |
| jclass family_class = env->FindClass( |
| "com/google/devtools/build/lib/profiler/" |
| "SystemNetworkStats$NetIfAddr$Family"); |
| const char *family_class_sig = |
| "Lcom/google/devtools/build/lib/profiler/" |
| "SystemNetworkStats$NetIfAddr$Family;"; |
| jfieldID family_af_inet_id = |
| env->GetStaticFieldID(family_class, "AF_INET", family_class_sig); |
| jobject family_af_inet = |
| env->GetStaticObjectField(family_class, family_af_inet_id); |
| jfieldID family_af_inet6_id = |
| env->GetStaticFieldID(family_class, "AF_INET6", family_class_sig); |
| jobject family_af_inet6 = |
| env->GetStaticObjectField(family_class, family_af_inet6_id); |
| jfieldID family_unknown_id = |
| env->GetStaticFieldID(family_class, "UNKNOWN", family_class_sig); |
| jobject family_unknown = |
| env->GetStaticObjectField(family_class, family_unknown_id); |
| |
| for (ifaddrs *ifa = ifaddr; ifa != nullptr; ifa = ifa->ifa_next) { |
| if (!ifa->ifa_addr) { |
| continue; |
| } |
| jstring name = env->NewStringUTF(ifa->ifa_name); |
| |
| int family = ifa->ifa_addr->sa_family; |
| |
| jobject family_enum; |
| switch (family) { |
| case AF_INET: |
| family_enum = family_af_inet; |
| break; |
| case AF_INET6: |
| family_enum = family_af_inet6; |
| break; |
| default: |
| family_enum = family_unknown; |
| } |
| |
| char buf[NI_MAXHOST]; |
| int err = convert_ipaddr(ifa->ifa_addr, family, buf, sizeof(buf)); |
| if (err != 0) { |
| PostException(env, errno, "convert_ipaddr"); |
| return; |
| } |
| jstring ipaddr = env->NewStringUTF(buf); |
| |
| jobject addr = env->CallStaticObjectMethod(addr_class, addr_create, name, |
| family_enum, ipaddr); |
| env->CallObjectMethod(addrs_list, list_add, addr); |
| } |
| |
| freeifaddrs(ifaddr); |
| } |
| |
| } // namespace blaze_jni |