| // 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 <jni.h> | 
 |  | 
 | #include <errno.h> | 
 | #include <stdio.h> | 
 | #include <stdlib.h> | 
 | #include <string.h> | 
 | #include <unistd.h> | 
 | #include <poll.h> | 
 | #include <sys/types.h> | 
 | #include <sys/socket.h> | 
 | #include <sys/un.h> | 
 |  | 
 | #include <string> | 
 |  | 
 | #include "src/main/native/unix_jni.h" | 
 |  | 
 | // Returns the field ID for FileDescriptor.fd. | 
 | static jfieldID GetFileDescriptorField(JNIEnv *env) { | 
 |   // See http://java.sun.com/docs/books/jni/html/fldmeth.html#26855 | 
 |   static jclass fd_class = NULL; | 
 |   if (fd_class == NULL) { /* note: harmless race condition */ | 
 |     jclass local = env->FindClass("java/io/FileDescriptor"); | 
 |     CHECK(local != NULL); | 
 |     fd_class = static_cast<jclass>(env->NewGlobalRef(local)); | 
 |   } | 
 |   static jfieldID fieldId = NULL; | 
 |   if (fieldId == NULL) { /* note: harmless race condition */ | 
 |     fieldId = env->GetFieldID(fd_class, "fd", "I"); | 
 |     CHECK(fieldId != NULL); | 
 |   } | 
 |   return fieldId; | 
 | } | 
 |  | 
 | // Returns the UNIX filedescriptor from a java.io.FileDescriptor instance. | 
 | static jint GetUnixFileDescriptor(JNIEnv *env, jobject fd_obj) { | 
 |   return env->GetIntField(fd_obj, GetFileDescriptorField(env)); | 
 | } | 
 |  | 
 | // Sets the UNIX filedescriptor of a java.io.FileDescriptor instance. | 
 | static void SetUnixFileDescriptor(JNIEnv *env, jobject fd_obj, jint fd) { | 
 |   env->SetIntField(fd_obj, GetFileDescriptorField(env), fd); | 
 | } | 
 |  | 
 | /* | 
 |  * Class:     com.google.devtools.build.lib.unix.LocalSocket | 
 |  * Method:    socket | 
 |  * Signature: (Ljava/io/FileDescriptor;)V | 
 |  */ | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocket_socket(JNIEnv *env, | 
 |                                                jclass clazz, | 
 |                                                jobject fd_sock) { | 
 |   int sock; | 
 |   if ((sock = ::socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { | 
 |     ::PostException(env, errno, ::ErrorMessage(errno)); | 
 |     return; | 
 |   } | 
 |   SetUnixFileDescriptor(env, fd_sock, sock); | 
 | } | 
 |  | 
 | // Initialize "addr" from "name_chars", reporting error and returning | 
 | // false on failure. | 
 | static bool InitializeSockaddr(JNIEnv *env, | 
 |                                struct sockaddr_un *addr, | 
 |                                const char* name_chars) { | 
 |   memset(addr, 0, sizeof *addr); | 
 |   addr->sun_family = AF_UNIX; | 
 |   // Note: UNIX_PATH_MAX is quite small! | 
 |   if (strlen(name_chars) >= sizeof(addr->sun_path)) { | 
 |     ::PostFileException(env, ENAMETOOLONG, name_chars); | 
 |     return false; | 
 |   } | 
 |   strcpy((char*) &addr->sun_path, name_chars); | 
 |   return true; | 
 | } | 
 |  | 
 | /* | 
 |  * Class:     com.google.devtools.build.lib.unix.LocalSocket | 
 |  * Method:    bind | 
 |  * Signature: (Ljava/io/FileDescriptor;Ljava/lang/String;)V | 
 |  */ | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocket_bind(JNIEnv *env, | 
 |                                              jclass clazz, | 
 |                                              jobject fd_svr, | 
 |                                              jstring name) { | 
 |   int svr_sock = GetUnixFileDescriptor(env, fd_svr); | 
 |   const char* name_chars = env->GetStringUTFChars(name, NULL); | 
 |   struct sockaddr_un addr; | 
 |   if (InitializeSockaddr(env, &addr, name_chars) && | 
 |       ::bind(svr_sock, (struct sockaddr *) &addr, sizeof addr) < 0) { | 
 |     ::PostException(env, errno, ::ErrorMessage(errno)); | 
 |   } | 
 |   env->ReleaseStringUTFChars(name, name_chars); | 
 | } | 
 |  | 
 | /* | 
 |  * Class:     com.google.devtools.build.lib.unix.LocalSocket | 
 |  * Method:    listen | 
 |  * Signature: (Ljava/io/FileDescriptor;I)V | 
 |  */ | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocket_listen(JNIEnv *env, | 
 |                                                jclass clazz, | 
 |                                                jobject fd_svr, | 
 |                                                jint backlog) { | 
 |   int svr_sock = GetUnixFileDescriptor(env, fd_svr); | 
 |   if (::listen(svr_sock, backlog) < 0) { | 
 |     ::PostException(env, errno, ::ErrorMessage(errno)); | 
 |   } | 
 | } | 
 |  | 
 | /* | 
 |  * Class:     com.google.devtools.build.lib.unix.LocalSocket | 
 |  * Method:    select | 
 |  * Signature: (L[java/io/FileDescriptor;[java/io/FileDescriptor;[java/io/FileDescriptor;J)Ljava/io/FileDescriptor | 
 |  */ | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocket_poll(JNIEnv *env, | 
 |                                                jclass clazz, | 
 |                                                jobject rfds_svr, | 
 |                                                jlong timeoutMillis) { | 
 |   // TODO(bazel-team): Handle Unix signals, namely SIGTERM. | 
 |  | 
 |   // Copy Java FD into pollfd | 
 |   pollfd pollfd; | 
 |   pollfd.fd = GetUnixFileDescriptor(env, rfds_svr); | 
 |   pollfd.events = POLLIN; | 
 |   pollfd.revents = 0; | 
 |  | 
 |   int count = poll(&pollfd, 1, timeoutMillis); | 
 |   if (count == 0) { | 
 |     // throws a timeout exception. | 
 |     ::PostException(env, ETIMEDOUT, ::ErrorMessage(ETIMEDOUT)); | 
 |   } else if (count < 0) { | 
 |     ::PostException(env, errno, ::ErrorMessage(errno)); | 
 |   } | 
 | } | 
 |  | 
 | /* | 
 |  * Class:     com.google.devtools.build.lib.unix.LocalSocket | 
 |  * Method:    accept | 
 |  * Signature: (Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;)V | 
 |  */ | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocket_accept(JNIEnv *env, | 
 |                                                jclass clazz, | 
 |                                                jobject fd_svr, | 
 |                                                jobject fd_cli) { | 
 |   int svr_sock = GetUnixFileDescriptor(env, fd_svr); | 
 |   int cli_sock; | 
 |   if ((cli_sock = ::accept(svr_sock, NULL, NULL)) < 0) { | 
 |     ::PostException(env, errno, ::ErrorMessage(errno)); | 
 |     return; | 
 |   } | 
 |   SetUnixFileDescriptor(env, fd_cli, cli_sock); | 
 | } | 
 |  | 
 | /* | 
 |  * Class:     com.google.devtools.build.lib.unix.LocalSocket | 
 |  * Method:    close | 
 |  * Signature: (Ljava/io/FileDescriptor;)V | 
 |  */ | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocket_close(JNIEnv *env, | 
 |                                               jclass clazz, | 
 |                                               jobject fd_svr) { | 
 |   int svr_sock = GetUnixFileDescriptor(env, fd_svr); | 
 |   if (::close(svr_sock) < 0) { | 
 |     ::PostException(env, errno, ::ErrorMessage(errno)); | 
 |   } | 
 |   SetUnixFileDescriptor(env, fd_svr, -1); | 
 | } | 
 |  | 
 | /* | 
 |  * Class:     com.google.devtools.build.lib.unix.LocalSocket | 
 |  * Method:    connect | 
 |  * Signature: (Ljava/io/FileDescriptor;Ljava/lang/String;)V | 
 |  */ | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocket_connect(JNIEnv *env, | 
 |                                                 jclass clazz, | 
 |                                                 jobject fd_cli, | 
 |                                                 jstring name) { | 
 |   const char* name_chars = env->GetStringUTFChars(name, NULL); | 
 |   jint cli_sock = GetUnixFileDescriptor(env, fd_cli); | 
 |   if (cli_sock == -1) { | 
 |     ::PostFileException(env, ENOTSOCK, name_chars); | 
 |   } else { | 
 |     struct sockaddr_un addr; | 
 |     if (InitializeSockaddr(env, &addr, name_chars)) { | 
 |       if (::connect(cli_sock, (struct sockaddr *) &addr, sizeof addr) < 0) { | 
 |         ::PostException(env, errno, ::ErrorMessage(errno)); | 
 |       } | 
 |     } | 
 |   } | 
 |   env->ReleaseStringUTFChars(name, name_chars); | 
 | } | 
 |  | 
 | /* | 
 |  * Class:     com.google.devtools.build.lib.unix.LocalSocket | 
 |  * Method:    shutdown() | 
 |  * Signature: (Ljava/io/FileDescriptor;I)V | 
 |  * Parameters: code: 0 to shutdown input and 1 to shutdown output. | 
 |  */ | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocket_shutdown(JNIEnv *env, | 
 |                                                  jclass clazz, | 
 |                                                  jobject fd, | 
 |                                                  jint code) { | 
 |   int action; | 
 |   if (code == 0) { | 
 |     action = SHUT_RD; | 
 |   } else { | 
 |     CHECK(code == 1); | 
 |     action = SHUT_WR; | 
 |   } | 
 |  | 
 |   int sock = GetUnixFileDescriptor(env, fd); | 
 |   if (shutdown(sock, action) < 0) { | 
 |     ::PostException(env, errno, ::ErrorMessage(errno)); | 
 |   } | 
 | } | 
 |  | 
 | // TODO(bazel-team): These methods were removed in JDK8, so they | 
 | // can be removed when we are no longer using JDK7.  See note in | 
 | // LocalSocketImpl. | 
 | static jmethodID increment_use_count_; | 
 | static jmethodID decrement_use_count_; | 
 |  | 
 | // >=JDK8 | 
 | static jmethodID fd_attach_; | 
 | static jmethodID fd_close_all_; | 
 |  | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocketImpl_init(JNIEnv *env, jclass ignored) { | 
 |   jclass cls = env->FindClass("java/io/FileDescriptor"); | 
 |   if (cls == NULL) { | 
 |     cls = env->FindClass("java/lang/NoClassDefFoundError"); | 
 |     env->ThrowNew(cls, "FileDescriptor class not found"); | 
 |     return; | 
 |   } | 
 |  | 
 |   // JDK7 | 
 |   increment_use_count_ = | 
 |       env->GetMethodID(cls, "incrementAndGetUseCount", "()I"); | 
 |   if (increment_use_count_ != NULL) { | 
 |     decrement_use_count_ = | 
 |         env->GetMethodID(cls, "decrementAndGetUseCount", "()I"); | 
 |   } else { | 
 |     // JDK8 | 
 |     env->ExceptionClear();  // The pending exception from increment_use_count_ | 
 |  | 
 |     fd_attach_ = env->GetMethodID(cls, "attach", "(Ljava/io/Closeable;)V"); | 
 |     fd_close_all_ = env->GetMethodID(cls, "closeAll", "(Ljava/io/Closeable;)V"); | 
 |  | 
 |     if (fd_attach_ == NULL || fd_close_all_ == NULL) { | 
 |       cls = env->FindClass("java/lang/NoSuchMethodError"); | 
 |       env->ThrowNew(cls, "FileDescriptor methods not found"); | 
 |       return; | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT void JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocketImpl_ref(JNIEnv *env, jclass clazz, | 
 |                                                 jobject fd, jobject closer) { | 
 |   if (increment_use_count_ != NULL) { | 
 |     env->CallIntMethod(fd, increment_use_count_); | 
 |   } | 
 |  | 
 |   if (fd_attach_ != NULL) { | 
 |     env->CallVoidMethod(fd, fd_attach_, closer); | 
 |   } | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jboolean JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocketImpl_unref(JNIEnv *env, jclass clazz, | 
 |                                                   jobject fd) { | 
 |   if (decrement_use_count_ != NULL) { | 
 |     env->CallIntMethod(fd, decrement_use_count_); | 
 |     return true; | 
 |   } | 
 |   return false; | 
 | } | 
 |  | 
 | extern "C" JNIEXPORT jboolean JNICALL | 
 | Java_com_google_devtools_build_lib_unix_LocalSocketImpl_close0(JNIEnv *env, jclass clazz, | 
 |                                                    jobject fd, | 
 |                                                    jobject closeable) { | 
 |   if (fd_close_all_ != NULL) { | 
 |     env->CallVoidMethod(fd, fd_close_all_, closeable); | 
 |     return true; | 
 |   } | 
 |   // This will happen if fd_close_all_ is NULL, which means we are running in | 
 |   // <=JDK7, which means that the caller needs to invoke close() explicitly. | 
 |   return false; | 
 | } |