// 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 #include // 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 // NOLINT #include #include #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 { 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 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(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(env->NewGlobalRef(lookup_result)); } static jmethodID getConstructorID(JNIEnv *env, jclass clazz, const char *parameters) { jmethodID method = env->GetMethodID(clazz, "", 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(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) { 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(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"); 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 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 == 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& dir_path, const char* entry) { std::vector::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 on success. Returns NULL on error and posts an // exception. static DIR* ForceOpendir(JNIEnv* env, const std::vector& 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 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) { PostDeleteTreesBelowException(env, errno, "fchmod", dir_path, nullptr); return nullptr; } } if (fchmodat(dir_fd, entry, 0700, 0) == -1) { PostDeleteTreesBelowException(env, errno, "fchmodat", dir_path, entry); return nullptr; } fd = openat(dir_fd, entry, flags); if (fd == -1) { PostDeleteTreesBelowException(env, errno, "opendir", dir_path, entry); return nullptr; } } DIR* dir = fdopendir(fd); if (dir == nullptr) { PostDeleteTreesBelowException(env, errno, "fdopendir", dir_path, entry); close(fd); return nullptr; } return dir; } // 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 on success. Returns -1 on error and posts an exception. static int ForceDelete(JNIEnv* env, const std::vector& 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 (fchmod(dir_fd, 0700) == -1) { PostDeleteTreesBelowException(env, errno, "fchmod", dir_path, nullptr); return -1; } if (unlinkat(dir_fd, entry, flags) == -1) { 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& 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) { 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* dir_path, const int dir_fd, const char* entry) { DIR *dir = ForceOpendir(env, *dir_path, dir_fd, entry); if (dir == nullptr) { 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://support.apple.com/kb/TA21420; 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. std::vector dir_files, dir_subdirs; for (;;) { errno = 0; struct dirent* de = readdir(dir); if (de == nullptr) { if (errno != 0) { 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 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(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