// 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 #include #include #include #include #include #include #include #include #include #include #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(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; }