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