diff options
Diffstat (limited to 'src/main/native/localsocket.cc')
-rw-r--r-- | src/main/native/localsocket.cc | 312 |
1 files changed, 312 insertions, 0 deletions
diff --git a/src/main/native/localsocket.cc b/src/main/native/localsocket.cc new file mode 100644 index 0000000000..56c48172dc --- /dev/null +++ b/src/main/native/localsocket.cc @@ -0,0 +1,312 @@ +// 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 <unistd.h> +#include <poll.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <sys/un.h> + +#include <string> + +#include "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; +} |