// 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);
}

/*
 * 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
