blob: 3731619e528b4c61a9ad1db62830187d6917e158 [file] [log] [blame]
// 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;
}