text: // Copyright 2014 Google Inc. All rights reserved.
  //
  // Licensed under the Apache License, Version 2.0 (the "License");
  // you may not use this file except in compliance with the License.
  // You may obtain a copy of the License at
  //
  //    http://www.apache.org/licenses/LICENSE-2.0
  //
  // Unless required by applicable law or agreed to in writing, software
  // distributed under the License is distributed on an "AS IS" BASIS,
  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  // See the License for the specific language governing permissions and
  // limitations under the License.
  
  #include "src/main/native/unix_jni.h"
  
  #include <jni.h>
  #include <dirent.h>
  #include <errno.h>
  #include <fcntl.h>
  #include <limits.h>
  #include <stdlib.h>
  #include <string.h>
  #include <sys/resource.h>
  #include <sys/stat.h>
  #include <sys/syscall.h>
  #include <sys/time.h>
  #include <sys/types.h>
  #include <unistd.h>
  #include <utime.h>
  
  #include <string>
  #include <vector>
  
  #include "src/main/native/macros.h"
  #include "src/main/cpp/util/md5.h"
  #include "src/main/cpp/util/port.h"
  
  using blaze_util::Md5Digest;
  
  ////////////////////////////////////////////////////////////////////////
  // Latin1 <--> java.lang.String conversion functions.
  // Derived from similar routines in Sun JDK.  See:
  // j2se/src/solaris/native/java/io/UnixFileSystem_md.c
  // j2se/src/share/native/common/jni_util.c
  //
  // Like the Sun JDK in its usual configuration, we assume all UNIX
  // filenames are Latin1 encoded.
  
  /**
   * Returns a new Java String for the specified Latin1 characters.
   */
  static jstring NewStringLatin1(JNIEnv *env, const char *str) {
      int len = strlen(str);
      jchar buf[512];
      jchar *str1;
  
      if (len > 512) {
        str1 = reinterpret_cast<jchar *>(malloc(len * sizeof(jchar)));
        if (str1 == 0) {
          ::PostException(env, ENOMEM, "Out of memory in NewStringLatin1");
          return NULL;
        }
      } else {
        str1 = buf;
      }
  
      for (int i = 0; i < len ; i++) {
        str1[i] = (unsigned char) str[i];
      }
      jstring result = env->NewString(str1, len);
      if (str1 != buf) {
        free(str1);
      }
      return result;
  }
  
  /**
   * Returns a nul-terminated Latin1-encoded byte array for the
   * specified Java string, or null on failure.  Unencodable characters
   * are replaced by '?'.  Must be followed by a call to
   * ReleaseStringLatin1Chars.
   */
  static const char *GetStringLatin1Chars(JNIEnv *env, jstring jstr) {
      jint len = env->GetStringLength(jstr);
      const jchar *str = env->GetStringCritical(jstr, NULL);
      if (str == NULL) {
        return NULL;
      }
  
      char *result = reinterpret_cast<char *>(malloc(len + 1));
      if (result == NULL) {
        env->ReleaseStringCritical(jstr, str);
        ::PostException(env, ENOMEM, "Out of memory in GetStringLatin1Chars");
        return NULL;
      }
  
      for (int i = 0; i < len; i++) {
        jchar unicode = str[i];  // (unsigned)
        result[i] = unicode <= 0x00ff ? unicode : '?';
      }
  
      result[len] = 0;
      env->ReleaseStringCritical(jstr, str);
      return result;
  }
  
  /**
   * Release the Latin1 chars returned by a prior call to
   * GetStringLatin1Chars.
   */
  static void ReleaseStringLatin1Chars(const char *s) {
    if (s != NULL) {
      free(const_cast<char *>(s));
    }
  }
  
  ////////////////////////////////////////////////////////////////////////
  
  // See unix_jni.h.
  void PostException(JNIEnv *env, int error_number, const std::string& message) {
    // Keep consistent with package-info.html!
    //
    // See /usr/include/asm/errno.h for UNIX error messages.
    // Select the most appropriate Java exception for a given UNIX error number.
    // (Consistent with errors generated by java.io package.)
    const char *exception_classname;
    switch (error_number) {
      case EFAULT:  // Illegal pointer--not likely
      case EBADF:   // Bad file number
        exception_classname = "java/lang/IllegalArgumentException";
        break;
      case ETIMEDOUT:  // Local socket timed out
        exception_classname = "java/net/SocketTimeoutException";
        break;
      case ENOENT:  // No such file or directory
        exception_classname = "java/io/FileNotFoundException";
        break;
      case EACCES:  // Permission denied
        exception_classname = "com/google/devtools/build/lib/unix/FileAccessException";
        break;
      case EPERM:   // Operation not permitted
        exception_classname = "com/google/devtools/build/lib/unix/FilePermissionException";
        break;
      case EINTR:   // Interrupted system call
        exception_classname = "java/io/InterruptedIOException";
        break;
      case ENOMEM:  // Out of memory
        exception_classname = "java/lang/OutOfMemoryError";
        break;
      case ENOSYS:   // Function not implemented
      case ENOTSUP:  // Operation not supported on transport endpoint
                     // (aka EOPNOTSUPP)
        exception_classname = "java/lang/UnsupportedOperationException";
        break;
      case ENAMETOOLONG:  // File name too long
      case ENODATA:    // No data available
      case EINVAL:     // Invalid argument
      case EMULTIHOP:  // Multihop attempted
      case ENOLINK:    // Link has been severed
      case EIO:        // I/O error
      case EAGAIN:     // Try again
      case EFBIG:      // File too large
      case EPIPE:      // Broken pipe
      case ENOSPC:     // No space left on device
      case EXDEV:      // Cross-device link
      case EROFS:      // Read-only file system
      case EEXIST:     // File exists
      case EMLINK:     // Too many links
      case ELOOP:      // Too many symbolic links encountered
      case EISDIR:     // Is a directory
      case ENOTDIR:    // Not a directory
      case ENOTEMPTY:  // Directory not empty
      case EBUSY:      // Device or resource busy
      case ENFILE:     // File table overflow
      case EMFILE:     // Too many open files
      default:
        exception_classname = "java/io/IOException";
    }
    jclass exception_class = env->FindClass(exception_classname);
    if (exception_class != NULL) {
       env->ThrowNew(exception_class, message.c_str());
    } else {
      abort();  // panic!
    }
  }
  
  // Throws RuntimeExceptions for IO operations which fail unexpectedly.
  // See package-info.html.
  // Returns true iff an exception was thrown.
  static bool PostRuntimeException(JNIEnv *env, int error_number,
                                   const char *file_path) {
    const char *exception_classname;
    switch (error_number) {
      case EFAULT:   // Illegal pointer--not likely
      case EBADF:    // Bad file number
        exception_classname = "java/lang/IllegalArgumentException";
        break;
      case ENOMEM:   // Out of memory
        exception_classname = "java/lang/OutOfMemoryError";
        break;
      case ENOTSUP:  // Operation not supported on transport endpoint
                     // (aka EOPNOTSUPP)
        exception_classname = "java/lang/UnsupportedOperationException";
        break;
      default:
        exception_classname = NULL;
    }
  
    if (exception_classname == NULL) {
      return false;
    }
  
    jclass exception_class = env->FindClass(exception_classname);
    if (exception_class != NULL) {
       std::string message(file_path);
       message += " (";
       message += ErrorMessage(error_number);
       message += ")";
       env->ThrowNew(exception_class, message.c_str());
       return true;
    } else {
      abort();  // panic!
      return false;  // Not reachable.
    }
  }
  
  // See unix_jni.h.
  void PostFileException(JNIEnv *env, int error_number, const char *filename) {
    ::PostException(env, error_number,
                    std::string(filename) + " (" + ErrorMessage(error_number)
                    + ")");
  }
  
  // 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_FilesystemUtils_readlink(JNIEnv *env,
                                                       jclass clazz,
                                                       jstring path) {
    const char *path_chars = GetStringLatin1Chars(env, path);
    char target[PATH_MAX] = "";
    jstring r = NULL;
    if (readlink(path_chars, target, arraysize(target)) == -1) {
      ::PostFileException(env, errno, path_chars);
    } else {
      r = NewStringLatin1(env, target);
    }
    ReleaseStringLatin1Chars(path_chars);
    return r;
  }
  
  extern "C" JNIEXPORT void JNICALL
  Java_com_google_devtools_build_lib_unix_FilesystemUtils_chmod(JNIEnv *env,
                                                    jclass clazz,
                                                    jstring path,
                                                    jint mode) {
    const char *path_chars = GetStringLatin1Chars(env, path);
    if (chmod(path_chars, static_cast<int>(mode)) == -1) {
      ::PostFileException(env, errno, path_chars);
    }
    ReleaseStringLatin1Chars(path_chars);
  }
  
  static void link_common(JNIEnv *env,
                          jstring oldpath,
                          jstring newpath,
                          int (*link_function)(const char *, const char *)) {
    const char *oldpath_chars = GetStringLatin1Chars(env, oldpath);
    const char *newpath_chars = GetStringLatin1Chars(env, newpath);
    if (link_function(oldpath_chars, newpath_chars) == -1) {
      ::PostFileException(env, errno, newpath_chars);
    }
    ReleaseStringLatin1Chars(oldpath_chars);
    ReleaseStringLatin1Chars(newpath_chars);
  }
  
  extern "C" JNIEXPORT void JNICALL
  Java_com_google_devtools_build_lib_unix_FilesystemUtils_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_FilesystemUtils_symlink(JNIEnv *env,
                                                      jclass clazz,
                                                      jstring oldpath,
                                                      jstring newpath) {
    link_common(env, oldpath, newpath, ::symlink);
  }
  
  static jobject NewFileStatus(JNIEnv *env,
                               const portable_stat_struct &stat_ref) {
    static jclass file_status_class = NULL;
    if (file_status_class == NULL) {  // note: harmless race condition
      jclass local = env->FindClass("com/google/devtools/build/lib/unix/FileStatus");
      CHECK(local != NULL);
      file_status_class = static_cast<jclass>(env->NewGlobalRef(local));
    }
  
    static jmethodID method = NULL;
    if (method == NULL) {  // note: harmless race condition
      method = env->GetMethodID(file_status_class, "<init>", "(IIIIIIIJIJ)V");
      CHECK(method != NULL);
    }
  
    return env->NewObject(
        file_status_class, method, stat_ref.st_mode,
        StatSeconds(stat_ref, STAT_ATIME), StatNanoSeconds(stat_ref, STAT_ATIME),
        StatSeconds(stat_ref, STAT_MTIME), StatNanoSeconds(stat_ref, STAT_MTIME),
        StatSeconds(stat_ref, STAT_CTIME), StatNanoSeconds(stat_ref, STAT_CTIME),
        static_cast<jlong>(stat_ref.st_size),
        static_cast<int>(stat_ref.st_dev), static_cast<jlong>(stat_ref.st_ino));
  }
  
  static jobject NewErrnoFileStatus(JNIEnv *env,
                                    int saved_errno,
                                    const portable_stat_struct &stat_ref) {
    static jclass errno_file_status_class = NULL;
    if (errno_file_status_class == NULL) {  // note: harmless race condition
      jclass local = env->FindClass("com/google/devtools/build/lib/unix/ErrnoFileStatus");
      CHECK(local != NULL);
      errno_file_status_class = static_cast<jclass>(env->NewGlobalRef(local));
    }
  
    static jmethodID no_error_ctor = NULL;
    if (no_error_ctor == NULL) {  // note: harmless race condition
      no_error_ctor = env->GetMethodID(errno_file_status_class,
                                       "<init>", "(IIIIIIIJIJ)V");
      CHECK(no_error_ctor != NULL);
    }
  
    static jmethodID errorno_ctor = NULL;
    if (errorno_ctor == NULL) {  // note: harmless race condition
      errorno_ctor = env->GetMethodID(errno_file_status_class, "<init>", "(I)V");
      CHECK(errorno_ctor != NULL);
    }
  
    if (saved_errno != 0) {
      return env->NewObject(errno_file_status_class, errorno_ctor, errno);
    }
    return env->NewObject(
        errno_file_status_class, no_error_ctor, stat_ref.st_mode,
        StatSeconds(stat_ref, STAT_ATIME), StatNanoSeconds(stat_ref, STAT_ATIME),
        StatSeconds(stat_ref, STAT_MTIME), StatNanoSeconds(stat_ref, STAT_MTIME),
        StatSeconds(stat_ref, STAT_CTIME), StatNanoSeconds(stat_ref, STAT_CTIME),
        static_cast<jlong>(stat_ref.st_size), static_cast<int>(stat_ref.st_dev),
        static_cast<jlong>(stat_ref.st_ino));
  }
  
  static void SetIntField(JNIEnv *env,
                          const jclass &clazz,
                          const jobject &object,
                          const char *name,
                          int val) {
    jfieldID fid = env->GetFieldID(clazz, name, "I");
    CHECK(fid != NULL);
    env->SetIntField(object, fid, val);
  }
  
  extern "C" JNIEXPORT void JNICALL
  Java_com_google_devtools_build_lib_unix_ErrnoFileStatus_00024ErrnoConstants_initErrnoConstants(  // NOLINT
    JNIEnv *env, jobject errno_constants) {
    jclass clazz = env->GetObjectClass(errno_constants);
    SetIntField(env, clazz, errno_constants, "ENOENT", ENOENT);
    SetIntField(env, clazz, errno_constants, "EACCES", EACCES);
    SetIntField(env, clazz, errno_constants, "ELOOP", ELOOP);
    SetIntField(env, clazz, errno_constants, "ENOTDIR", ENOTDIR);
    SetIntField(env, clazz, errno_constants, "ENAMETOOLONG", ENAMETOOLONG);
  }
  
  static jobject StatCommon(JNIEnv *env,
                            jstring path,
                            int (*stat_function)(const char *, portable_stat_struct *),
                            bool should_throw) {
    portable_stat_struct statbuf;
    const char *path_chars = GetStringLatin1Chars(env, path);
    int r;
    int saved_errno = 0;
    while ((r = stat_function(path_chars, &statbuf)) == -1 && errno == EINTR) { }
    if (r == -1) {
      // EACCES ENOENT ENOTDIR ELOOP -> IOException
      // ENAMETOOLONGEFAULT          -> RuntimeException
      // ENOMEM                      -> OutOfMemoryError
  
      if (PostRuntimeException(env, errno, path_chars)) {
        ::ReleaseStringLatin1Chars(path_chars);
        return NULL;
      } else if (should_throw) {
        ::PostFileException(env, errno, path_chars);
        ::ReleaseStringLatin1Chars(path_chars);
        return NULL;
      } else {
        saved_errno = errno;
      }
    }
    ::ReleaseStringLatin1Chars(path_chars);
  
    return should_throw
      ? NewFileStatus(env, statbuf)
      : NewErrnoFileStatus(env, saved_errno, statbuf);
  }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * 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_FilesystemUtils_stat(JNIEnv *env,
                                                   jclass clazz,
                                                   jstring path) {
    return ::StatCommon(env, path, portable_stat, true);
  }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * 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_FilesystemUtils_lstat(JNIEnv *env,
                                                    jclass clazz,
                                                    jstring path) {
    return ::StatCommon(env, path, portable_lstat, true);
  }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * 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_FilesystemUtils_errnoStat(JNIEnv *env,
                                                        jclass clazz,
                                                        jstring path) {
    return ::StatCommon(env, path, portable_stat, false);
  }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * 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_FilesystemUtils_errnoLstat(JNIEnv *env,
                                                         jclass clazz,
                                                         jstring path) {
    return ::StatCommon(env, path, portable_lstat, false);
  }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * Method:    utime
   * Signature: (Ljava/lang/String;ZII)V
   * Throws:    java.io.IOException
   */
  extern "C" JNIEXPORT void JNICALL
  Java_com_google_devtools_build_lib_unix_FilesystemUtils_utime(JNIEnv *env,
                                                    jclass clazz,
                                                    jstring path,
                                                    jboolean now,
                                                    jint actime,
                                                    jint modtime) {
    const char *path_chars = GetStringLatin1Chars(env, path);
    struct utimbuf buf = { actime, modtime };
    struct utimbuf *bufptr = now ? NULL : &buf;
    if (::utime(path_chars, bufptr) == -1) {
      // EACCES ENOENT EMULTIHOP ELOOP EINTR
      // ENOTDIR ENOLINK EPERM EROFS   -> IOException
      // EFAULT ENAMETOOLONG           -> RuntimeException
      ::PostFileException(env, errno, path_chars);
    }
    ReleaseStringLatin1Chars(path_chars);
  }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * Method:    umask
   * Signature: (I)I
   */
  extern "C" JNIEXPORT jint JNICALL
  Java_com_google_devtools_build_lib_unix_FilesystemUtils_umask(JNIEnv *env,
                                                    jclass clazz,
                                                    jint new_umask) {
    return ::umask(new_umask);
  }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * Method:    mkdir
   * Signature: (Ljava/lang/String;I)Z
   * Throws:    java.io.IOException
   */
  extern "C" JNIEXPORT jboolean JNICALL
  Java_com_google_devtools_build_lib_unix_FilesystemUtils_mkdir(JNIEnv *env,
                                                    jclass clazz,
                                                    jstring path,
                                                    jint mode) {
    const char *path_chars = GetStringLatin1Chars(env, path);
    jboolean result = true;
    if (::mkdir(path_chars, mode) == -1) {
      // EACCES ENOENT ELOOP
      // ENOSPC ENOTDIR EPERM EROFS     -> IOException
      // EFAULT ENAMETOOLONG            -> RuntimeException
      // ENOMEM                         -> OutOfMemoryError
      // EEXIST                         -> return false
      if (errno == EEXIST) {
        result = false;
      } else {
        ::PostFileException(env, errno, path_chars);
      }
    }
    ReleaseStringLatin1Chars(path_chars);
    return result;
  }
  
  static jobject NewDirents(JNIEnv *env,
                            jobjectArray names,
                            jbyteArray types) {
    // See http://java.sun.com/docs/books/jni/html/fldmeth.html#26855
    static jclass dirents_class = NULL;
    if (dirents_class == NULL) {  // note: harmless race condition
      jclass local = env->FindClass("com/google/devtools/build/lib/unix/FilesystemUtils$Dirents");
      CHECK(local != NULL);
      dirents_class = static_cast<jclass>(env->NewGlobalRef(local));
    }
  
    static jmethodID ctor = NULL;
    if (ctor == NULL) {  // note: harmless race condition
      ctor = env->GetMethodID(dirents_class, "<init>", "([Ljava/lang/String;[B)V");
      CHECK(ctor != NULL);
    }
  
    return env->NewObject(dirents_class, ctor, names, types);
  }
  
  static char GetDirentType(struct dirent *entry,
                            int dirfd,
                            bool follow_symlinks) {
    switch (entry->d_type) {
      case DT_REG:
        return 'f';
      case DT_DIR:
        return 'd';
      case DT_LNK:
        if (!follow_symlinks) {
          return 's';
        }
        FALLTHROUGH_INTENDED;
      case DT_UNKNOWN:
        portable_stat_struct statbuf;
        if (portable_fstatat(dirfd, entry->d_name, &statbuf, 0) == 0) {
          if (S_ISREG(statbuf.st_mode)) return 'f';
          if (S_ISDIR(statbuf.st_mode)) return 'd';
        }
        // stat failed or returned something weird; fall through
        FALLTHROUGH_INTENDED;
      default:
        return '?';
    }
  }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * 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_FilesystemUtils_readdir(JNIEnv *env,
                                                      jclass clazz,
                                                      jstring path,
                                                      jchar read_types) {
    const char *path_chars = GetStringLatin1Chars(env, path);
    DIR *dirh;
    while ((dirh = ::opendir(path_chars)) == NULL && errno == EINTR) { }
    if (dirh == NULL) {
      // EACCES EMFILE ENFILE ENOENT ENOTDIR -> IOException
      // ENOMEM                              -> OutOfMemoryError
      ::PostFileException(env, errno, path_chars);
    }
    ReleaseStringLatin1Chars(path_chars);
    if (dirh == NULL) {
      return NULL;
    }
    int fd = dirfd(dirh);
  
    std::vector<std::string> entries;
    std::vector<jbyte> types;
    for (;;) {
      // Clear errno beforehand.  Because readdir() is not required to clear it at
      // EOF, this is the only way to reliably distinguish EOF from error.
      errno = 0;
      struct dirent *entry = ::readdir(dirh);
      if (entry == NULL) {
        if (errno == 0) break;  // EOF
        // It is unclear whether an error can also skip some records.
        // That does not appear to happen with glibc, at least.
        if (errno == EINTR) continue;  // interrupted by a signal
        if (errno == EIO) continue;  // glibc returns this on transient errors
        // Otherwise, this is a real error we should report.
        ::PostFileException(env, errno, path_chars);
        ::closedir(dirh);
        return NULL;
      }
      // Omit . and .. from results.
      if (entry->d_name[0] == '.') {
        if (entry->d_name[1] == '\0') continue;
        if (entry->d_name[1] == '.' && entry->d_name[2] == '\0') continue;
      }
      entries.push_back(entry->d_name);
      if (read_types != 'n') {
        types.push_back(GetDirentType(entry, fd, read_types == 'f'));
      }
    }
  
    if (::closedir(dirh) < 0 && errno != EINTR) {
      ::PostFileException(env, errno, path_chars);
      return NULL;
    }
  
    size_t len = entries.size();
    jclass jlStringClass = env->GetObjectClass(path);
    jobjectArray names_obj = env->NewObjectArray(len, jlStringClass, NULL);
    if (names_obj == NULL && env->ExceptionOccurred()) {
      return NULL;  // async exception!
    }
  
    for (size_t ii = 0; ii < len; ++ii) {
      jstring s = NewStringLatin1(env, entries[ii].c_str());
      if (s == NULL && env->ExceptionOccurred()) {
        return NULL;  // async exception!
      }
      env->SetObjectArrayElement(names_obj, ii, s);
    }
  
    jbyteArray types_obj = NULL;
    if (read_types != 'n') {
      CHECK(len == types.size());
      types_obj = env->NewByteArray(len);
      CHECK(types_obj);
      if (len > 0) {
        env->SetByteArrayRegion(types_obj, 0, len, &types[0]);
      }
    }
  
    return NewDirents(env, names_obj, types_obj);
  }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * 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_FilesystemUtils_rename(JNIEnv *env,
                                                     jclass clazz,
                                                     jstring oldpath,
                                                     jstring newpath) {
    const char *oldpath_chars = GetStringLatin1Chars(env, oldpath);
    const char *newpath_chars = GetStringLatin1Chars(env, newpath);
    if (::rename(oldpath_chars, newpath_chars) == -1) {
      // EISDIR EXDEV ENOTEMPTY EEXIST EBUSY
      // EINVAL EMLINK ENOTDIR EACCES EPERM
      // ENOENT EROFS ELOOP ENOSPC           -> IOException
      // EFAULT ENAMETOOLONG                 -> RuntimeException
      // ENOMEM                              -> OutOfMemoryError
      std::string filename(std::string(oldpath_chars) + " -> " + newpath_chars);
      ::PostFileException(env, errno, filename.c_str());
    }
    ReleaseStringLatin1Chars(oldpath_chars);
    ReleaseStringLatin1Chars(newpath_chars);
  }
  
  static bool delete_common(JNIEnv *env,
                            jstring path,
                            int (*delete_function)(const char *),
                            bool (*error_function)(int)) {
    const char *path_chars = GetStringLatin1Chars(env, path);
    if (path_chars == NULL) {
        return false;
    }
    bool ok = delete_function(path_chars) != -1;
    if (!ok) {
      if (!error_function(errno)) {
        ::PostFileException(env, errno, path_chars);
      }
    }
    ReleaseStringLatin1Chars(path_chars);
    return ok;
  }
  
  static bool unlink_err(int err) { return err == ENOENT; }
  static bool remove_err(int err) { return err == ENOENT || err == ENOTDIR; }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * Method:    unlink
   * Signature: (Ljava/lang/String;)V
   * Throws:    java.io.IOException
   */
  extern "C" JNIEXPORT bool JNICALL
  Java_com_google_devtools_build_lib_unix_FilesystemUtils_unlink(JNIEnv *env,
                                                     jclass clazz,
                                                     jstring path) {
    return ::delete_common(env, path, ::unlink, ::unlink_err);
  }
  
  /*
   * Class:     com.google.devtools.build.lib.unix.FilesystemUtils
   * Method:    remove
   * Signature: (Ljava/lang/String;)V
   * Throws:    java.io.IOException
   */
  extern "C" JNIEXPORT bool JNICALL
  Java_com_google_devtools_build_lib_unix_FilesystemUtils_remove(JNIEnv *env,
                                                     jclass clazz,
                                                     jstring path) {
    return ::delete_common(env, path, ::remove, ::remove_err);
  }
  
  
  ////////////////////////////////////////////////////////////////////////
  // Linux extended file attributes
  
  typedef ssize_t getxattr_func(const char *path, const char *name,
                                void *value, size_t size);
  
  static jbyteArray getxattr_common(JNIEnv *env,
                                    jstring path,
                                    jstring name,
                                    getxattr_func getxattr) {
    const char *path_chars = GetStringLatin1Chars(env, path);
    const char *name_chars = GetStringLatin1Chars(env, name);
  
    // TODO(bazel-team): on ERANGE, try again with larger buffer.
    jbyte value[4096];
    jbyteArray result = NULL;
    ssize_t size = getxattr(path_chars, name_chars, value, arraysize(value));
    if (size == -1) {
      if (errno != ENODATA) {
        ::PostFileException(env, errno, path_chars);
      }
    } else {
      result = env->NewByteArray(size);
      env->SetByteArrayRegion(result, 0, size, value);
    }
    ReleaseStringLatin1Chars(path_chars);
    ReleaseStringLatin1Chars(name_chars);
    return result;
  }
  
  extern "C" JNIEXPORT jbyteArray JNICALL
  Java_com_google_devtools_build_lib_unix_FilesystemUtils_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_FilesystemUtils_lgetxattr(JNIEnv *env,
                                                        jclass clazz,
                                                        jstring path,
                                                        jstring name) {
    return ::getxattr_common(env, path, name, ::portable_lgetxattr);
  }
  
  
  // Computes MD5 digest of "file", writes result in "result", which
  // must be of length Md5Digest::kDigestLength.  Returns zero on success, or
  // -1 (and sets errno) otherwise.
  static int md5sumAsBytes(const char *file,
                           jbyte result[Md5Digest::kDigestLength]) {
    Md5Digest digest;
    // OPT: Using a 32k buffer would give marginally better performance,
    // but what is the stack size here?
    jbyte buf[4096];
    int fd;
    while ((fd = open(file, O_RDONLY)) == -1 && errno == EINTR) { }
    if (fd == -1) {
      return -1;
    }
    for (ssize_t len = read(fd, buf, arraysize(buf));
         len != 0;
         len = read(fd, buf, arraysize(buf))) {
      if (len == -1) {
        if (errno == EINTR) {
          continue;
        } else {
          int read_errno = errno;
          close(fd);  // prefer read() errors over close().
          errno = read_errno;
          return -1;
        }
      }
      digest.Update(buf, len);
    }
    if (close(fd) < 0 && errno != EINTR) {
      return -1;
    }
    digest.Finish(reinterpret_cast<unsigned char*>(result));
    return 0;
  }
  
  
  extern "C" JNIEXPORT jbyteArray JNICALL
  Java_com_google_devtools_build_lib_unix_FilesystemUtils_md5sumAsBytes(
      JNIEnv *env, jclass clazz, jstring path) {
    const char *path_chars = GetStringLatin1Chars(env, path);
    jbyte value[Md5Digest::kDigestLength];
    jbyteArray result = NULL;
    if (md5sumAsBytes(path_chars, value) == 0) {
      result = env->NewByteArray(Md5Digest::kDigestLength);
      env->SetByteArrayRegion(result, 0, Md5Digest::kDigestLength, value);
    } else {
      ::PostFileException(env, errno, path_chars);
    }
    ReleaseStringLatin1Chars(path_chars);
    return result;
  }
  