summaryrefslogtreecommitdiffstats
path: root/luni/src/main/native/java_lang_ProcessManager.cpp
blob: 2a1638f6dee1f0efb570723d1b70a752b7508fae (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
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
/*
 * 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 "JniConstants.h"
#include "utils/Log.h"

/*
 * 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

/**
 * Loops indefinitely and calls ProcessManager.onExit() when children exit.
 */
static void ProcessManager_watchChildren(JNIEnv* env, jclass processManagerClass, jobject processManager) {
    static jmethodID onExitMethod = env->GetMethodID(processManagerClass, "onExit", "(II)V");
    if (onExitMethod == NULL) {
        return;
    }

    while (true) {
        // Wait for children in our process group.
        int status;
        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(processManager, 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 skipFd1, int skipFd2) {
    // TODO: rather than close all these non-open files, we could look in /proc/self/fd.
    rlimit rlimit;
    getrlimit(RLIMIT_NOFILE, &rlimit);
    const int max_fd = rlimit.rlim_max;
    for (int fd = 3; fd < max_fd; ++fd) {
        if (fd != skipFd1 && fd != skipFd2) {
            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) {
    for (int 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) {

    // Keep track of the system properties fd so we don't close it.
    int androidSystemPropertiesFd = -1;
    char* fdString = getenv("ANDROID_PROPERTY_WORKSPACE");
    if (fdString) {
        androidSystemPropertiesFd = atoi(fdString);
    }

    // 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 (int 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 unwanted open fds.
        closeNonStandardFds(statusOut, androidSystemPropertiesFd);

        // Switch to working directory.
        if (workingDirectory != NULL) {
            if (chdir(workingDirectory) == -1) {
                goto execFailed;
            }
        }

        // Set up environment.
        if (environment != NULL) {
            extern char** environ; // Standard, but not in any header file.
            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:
            int 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 result;
    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;
    }

    jsize length = env->GetArrayLength(javaArray);
    char** array = new char*[length + 1];
    array[length] = 0;
    for (jsize index = 0; index < length; index++) {
        jstring javaEntry = (jstring) env->GetObjectArrayElement(javaArray, index);
        // We need to pass these strings to const-unfriendly code.
        char* entry = const_cast<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);
    for (jsize index = 0; index < length; index++) {
        jstring javaEntry = reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, index));
        env->ReleaseStringUTFChars(javaEntry, array[index]);
    }

    delete[] array;
}

/**
 * Converts Java String[] to char** and delegates to executeProcess().
 */
static pid_t 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;
}

static JNINativeMethod methods[] = {
    NATIVE_METHOD(ProcessManager, watchChildren, "(Ljava/lang/ProcessManager;)V"),
    NATIVE_METHOD(ProcessManager, exec, "([Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Z)I"),
};
int register_java_lang_ProcessManager(JNIEnv* env) {
    return jniRegisterNativeMethods(env, "java/lang/ProcessManager", methods, NELEM(methods));
}