aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/native/localsocket.cc
blob: 3731619e528b4c61a9ad1db62830187d6917e158 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
// 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;
}