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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
|
/*
* Copyright (C) 2007 The Android Open Source Project
*
* 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.
*/
#define LOG_TAG "ProcessManager"
#include <sys/resource.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>
#include <fcntl.h>
#include <signal.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "jni.h"
#include "JNIHelp.h"
#include "utils/Log.h"
/** Environment variables. */
extern char **environ;
static jmethodID onExitMethod = NULL;
static jfieldID descriptorField = NULL;
#ifdef ANDROID
// Keeps track of the system properties fd so we don't close it.
static int androidSystemPropertiesFd = -1;
#endif
/*
* These are constants shared with the higher level code in
* ProcessManager.java.
*/
#define WAIT_STATUS_UNKNOWN (-1) // unknown child status
#define WAIT_STATUS_NO_CHILDREN (-2) // no children to wait for
#define WAIT_STATUS_STRANGE_ERRNO (-3) // observed an undocumented errno
/** Closes a file descriptor. */
static void java_lang_ProcessManager_close(JNIEnv* env,
jclass, jobject javaDescriptor) {
int fd = env->GetIntField(javaDescriptor, descriptorField);
if (TEMP_FAILURE_RETRY(close(fd)) == -1) {
jniThrowIOException(env, errno);
}
}
/**
* Kills process with the given ID.
*/
static void java_lang_ProcessManager_kill(JNIEnv* env, jclass, jint pid) {
int result = kill((pid_t) pid, SIGKILL);
if (result == -1) {
jniThrowIOException(env, errno);
}
}
/**
* Loops indefinitely and calls ProcessManager.onExit() when children exit.
*/
static void java_lang_ProcessManager_watchChildren(JNIEnv* env, jobject o) {
if (onExitMethod == NULL) {
jniThrowException(env, "java/lang/IllegalStateException",
"staticInitialize() must run first.");
}
while (1) {
int status;
/* wait for children in our process group */
pid_t pid = waitpid(0, &status, 0);
if (pid >= 0) {
// Extract real status.
if (WIFEXITED(status)) {
status = WEXITSTATUS(status);
} else if (WIFSIGNALED(status)) {
status = WTERMSIG(status);
} else if (WIFSTOPPED(status)) {
status = WSTOPSIG(status);
} else {
status = WAIT_STATUS_UNKNOWN;
}
} else {
/*
* The pid should be -1 already, but force it here just in case
* we somehow end up with some other negative value.
*/
pid = -1;
switch (errno) {
case ECHILD: {
/*
* Expected errno: There are no children to wait()
* for. The callback will sleep until it is
* informed of another child coming to life.
*/
status = WAIT_STATUS_NO_CHILDREN;
break;
}
case EINTR: {
/*
* An unblocked signal came in while waiting; just
* retry the wait().
*/
continue;
}
default: {
/*
* Unexpected errno, so squawk! Note: Per the
* Linux docs, there are no errnos defined for
* wait() other than the two that are handled
* immediately above.
*/
LOGE("Error %d calling wait(): %s", errno,
strerror(errno));
status = WAIT_STATUS_STRANGE_ERRNO;
break;
}
}
}
env->CallVoidMethod(o, onExitMethod, pid, status);
if (env->ExceptionOccurred()) {
/*
* The callback threw, so break out of the loop and return,
* letting the exception percolate up.
*/
break;
}
}
}
/** Close all open fds > 2 (i.e. everything but stdin/out/err), != skipFd. */
static void closeNonStandardFds(int skipFd) {
// TODO: rather than close all these non-open files, we could look in /proc/self/fd.
struct rlimit rlimit;
getrlimit(RLIMIT_NOFILE, &rlimit);
const int max_fd = rlimit.rlim_max;
for (int fd = 3; fd < max_fd; ++fd) {
if (fd != skipFd
#ifdef ANDROID
&& fd != androidSystemPropertiesFd
#endif
) {
close(fd);
}
}
}
#define PIPE_COUNT (4) // number of pipes used to communicate with child proc
/** Closes all pipes in the given array. */
static void closePipes(int pipes[], int skipFd) {
int i;
for (i = 0; i < PIPE_COUNT * 2; i++) {
int fd = pipes[i];
if (fd == -1) {
return;
}
if (fd != skipFd) {
close(pipes[i]);
}
}
}
/** Executes a command in a child process. */
static pid_t executeProcess(JNIEnv* env, char** commands, char** environment,
const char* workingDirectory, jobject inDescriptor,
jobject outDescriptor, jobject errDescriptor,
jboolean redirectErrorStream) {
int i, result, error;
// Create 4 pipes: stdin, stdout, stderr, and an exec() status pipe.
int pipes[PIPE_COUNT * 2] = { -1, -1, -1, -1, -1, -1, -1, -1 };
for (i = 0; i < PIPE_COUNT; i++) {
if (pipe(pipes + i * 2) == -1) {
jniThrowIOException(env, errno);
closePipes(pipes, -1);
return -1;
}
}
int stdinIn = pipes[0];
int stdinOut = pipes[1];
int stdoutIn = pipes[2];
int stdoutOut = pipes[3];
int stderrIn = pipes[4];
int stderrOut = pipes[5];
int statusIn = pipes[6];
int statusOut = pipes[7];
pid_t childPid = fork();
// If fork() failed...
if (childPid == -1) {
jniThrowIOException(env, errno);
closePipes(pipes, -1);
return -1;
}
// If this is the child process...
if (childPid == 0) {
/*
* Note: We cannot malloc() or free() after this point!
* A no-longer-running thread may be holding on to the heap lock, and
* an attempt to malloc() or free() would result in deadlock.
*/
// Replace stdin, out, and err with pipes.
dup2(stdinIn, 0);
dup2(stdoutOut, 1);
if (redirectErrorStream) {
dup2(stdoutOut, 2);
} else {
dup2(stderrOut, 2);
}
// Close all but statusOut. This saves some work in the next step.
closePipes(pipes, statusOut);
// Make statusOut automatically close if execvp() succeeds.
fcntl(statusOut, F_SETFD, FD_CLOEXEC);
// Close remaining open fds with the exception of statusOut.
closeNonStandardFds(statusOut);
// Switch to working directory.
if (workingDirectory != NULL) {
if (chdir(workingDirectory) == -1) {
goto execFailed;
}
}
// Set up environment.
if (environment != NULL) {
environ = environment;
}
// Execute process. By convention, the first argument in the arg array
// should be the command itself. In fact, I get segfaults when this
// isn't the case.
execvp(commands[0], commands);
// If we got here, execvp() failed or the working dir was invalid.
execFailed:
error = errno;
write(statusOut, &error, sizeof(int));
close(statusOut);
exit(error);
}
// This is the parent process.
// Close child's pipe ends.
close(stdinIn);
close(stdoutOut);
close(stderrOut);
close(statusOut);
// Check status pipe for an error code. If execvp() succeeds, the other
// end of the pipe should automatically close, in which case, we'll read
// nothing.
int count = read(statusIn, &result, sizeof(int));
close(statusIn);
if (count > 0) {
jniThrowIOException(env, result);
close(stdoutIn);
close(stdinOut);
close(stderrIn);
return -1;
}
// Fill in file descriptor wrappers.
jniSetFileDescriptorOfFD(env, inDescriptor, stdoutIn);
jniSetFileDescriptorOfFD(env, outDescriptor, stdinOut);
jniSetFileDescriptorOfFD(env, errDescriptor, stderrIn);
return childPid;
}
/** Converts a Java String[] to a 0-terminated char**. */
static char** convertStrings(JNIEnv* env, jobjectArray javaArray) {
if (javaArray == NULL) {
return NULL;
}
char** array = NULL;
jsize length = env->GetArrayLength(javaArray);
array = (char**) malloc(sizeof(char*) * (length + 1));
array[length] = 0;
jsize index;
for (index = 0; index < length; index++) {
jstring javaEntry =
(jstring) env->GetObjectArrayElement(javaArray, index);
char* entry = (char*) env->GetStringUTFChars(javaEntry, NULL);
array[index] = entry;
}
return array;
}
/** Frees a char** which was converted from a Java String[]. */
static void freeStrings(JNIEnv* env, jobjectArray javaArray, char** array) {
if (javaArray == NULL) {
return;
}
jsize length = env->GetArrayLength(javaArray);
jsize index;
for (index = 0; index < length; index++) {
jstring javaEntry =
(jstring) env->GetObjectArrayElement(javaArray, index);
env->ReleaseStringUTFChars(javaEntry, array[index]);
}
free(array);
}
/**
* Converts Java String[] to char** and delegates to executeProcess().
*/
static pid_t java_lang_ProcessManager_exec(
JNIEnv* env, jclass, jobjectArray javaCommands,
jobjectArray javaEnvironment, jstring javaWorkingDirectory,
jobject inDescriptor, jobject outDescriptor, jobject errDescriptor,
jboolean redirectErrorStream) {
// Copy commands into char*[].
char** commands = convertStrings(env, javaCommands);
// Extract working directory string.
const char* workingDirectory = NULL;
if (javaWorkingDirectory != NULL) {
workingDirectory = env->GetStringUTFChars(javaWorkingDirectory, NULL);
}
// Convert environment array.
char** environment = convertStrings(env, javaEnvironment);
pid_t result = executeProcess(
env, commands, environment, workingDirectory,
inDescriptor, outDescriptor, errDescriptor, redirectErrorStream);
// Temporarily clear exception so we can clean up.
jthrowable exception = env->ExceptionOccurred();
env->ExceptionClear();
freeStrings(env, javaEnvironment, environment);
// Clean up working directory string.
if (javaWorkingDirectory != NULL) {
env->ReleaseStringUTFChars(javaWorkingDirectory, workingDirectory);
}
freeStrings(env, javaCommands, commands);
// Re-throw exception if present.
if (exception != NULL) {
if (env->Throw(exception) < 0) {
LOGE("Error rethrowing exception!");
}
}
return result;
}
/**
* Looks up Java members.
*/
static void java_lang_ProcessManager_staticInitialize(JNIEnv* env,
jclass clazz) {
#ifdef ANDROID
char* fdString = getenv("ANDROID_PROPERTY_WORKSPACE");
if (fdString) {
androidSystemPropertiesFd = atoi(fdString);
}
#endif
onExitMethod = env->GetMethodID(clazz, "onExit", "(II)V");
if (onExitMethod == NULL) {
return;
}
jclass fileDescriptorClass = env->FindClass("java/io/FileDescriptor");
if (fileDescriptorClass == NULL) {
return;
}
descriptorField = env->GetFieldID(fileDescriptorClass, "descriptor", "I");
if (descriptorField == NULL) {
return;
}
}
static JNINativeMethod methods[] = {
{ "close", "(Ljava/io/FileDescriptor;)V", (void*) java_lang_ProcessManager_close },
{ "kill", "(I)V", (void*) java_lang_ProcessManager_kill },
{ "staticInitialize", "()V", (void*) java_lang_ProcessManager_staticInitialize },
{ "watchChildren", "()V", (void*) java_lang_ProcessManager_watchChildren },
{ "exec", "([Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Z)I",
(void*) java_lang_ProcessManager_exec },
};
int register_java_lang_ProcessManager(JNIEnv* env) {
return jniRegisterNativeMethods(env, "java/lang/ProcessManager", methods, NELEM(methods));
}
|