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