1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#define LOG_TAG "ProcessManager"
18
19#include <sys/resource.h>
20#include <sys/types.h>
21#include <unistd.h>
22#include <fcntl.h>
23#include <stdlib.h>
24#include <string.h>
25#include <errno.h>
26
27#include "jni.h"
28#include "JNIHelp.h"
29#include "JniConstants.h"
30#include "ScopedLocalRef.h"
31#include "cutils/log.h"
32
33/** Close all open fds > 2 (i.e. everything but stdin/out/err), != skipFd. */
34static void closeNonStandardFds(int skipFd1, int skipFd2) {
35    // TODO: rather than close all these non-open files, we could look in /proc/self/fd.
36    rlimit rlimit;
37    getrlimit(RLIMIT_NOFILE, &rlimit);
38    const int max_fd = rlimit.rlim_max;
39    for (int fd = 3; fd < max_fd; ++fd) {
40        if (fd != skipFd1 && fd != skipFd2) {
41            close(fd);
42        }
43    }
44}
45
46#define PIPE_COUNT (4) // number of pipes used to communicate with child proc
47
48/** Closes all pipes in the given array. */
49static void closePipes(int pipes[], int skipFd) {
50    for (int i = 0; i < PIPE_COUNT * 2; i++) {
51        int fd = pipes[i];
52        if (fd == -1) {
53            return;
54        }
55        if (fd != skipFd) {
56            close(pipes[i]);
57        }
58    }
59}
60
61/** Executes a command in a child process. */
62static pid_t executeProcess(JNIEnv* env, char** commands, char** environment,
63        const char* workingDirectory, jobject inDescriptor,
64        jobject outDescriptor, jobject errDescriptor,
65        jboolean redirectErrorStream) {
66
67    // Keep track of the system properties fd so we don't close it.
68    int androidSystemPropertiesFd = -1;
69    char* fdString = getenv("ANDROID_PROPERTY_WORKSPACE");
70    if (fdString) {
71        androidSystemPropertiesFd = atoi(fdString);
72    }
73
74    // Create 4 pipes: stdin, stdout, stderr, and an exec() status pipe.
75    int pipes[PIPE_COUNT * 2] = { -1, -1, -1, -1, -1, -1, -1, -1 };
76    for (int i = 0; i < PIPE_COUNT; i++) {
77        if (pipe(pipes + i * 2) == -1) {
78            jniThrowIOException(env, errno);
79            closePipes(pipes, -1);
80            return -1;
81        }
82    }
83    int stdinIn = pipes[0];
84    int stdinOut = pipes[1];
85    int stdoutIn = pipes[2];
86    int stdoutOut = pipes[3];
87    int stderrIn = pipes[4];
88    int stderrOut = pipes[5];
89    int statusIn = pipes[6];
90    int statusOut = pipes[7];
91
92    pid_t childPid = fork();
93
94    // If fork() failed...
95    if (childPid == -1) {
96        jniThrowIOException(env, errno);
97        closePipes(pipes, -1);
98        return -1;
99    }
100
101    // If this is the child process...
102    if (childPid == 0) {
103        /*
104         * Note: We cannot malloc() or free() after this point!
105         * A no-longer-running thread may be holding on to the heap lock, and
106         * an attempt to malloc() or free() would result in deadlock.
107         */
108
109        // Replace stdin, out, and err with pipes.
110        dup2(stdinIn, 0);
111        dup2(stdoutOut, 1);
112        if (redirectErrorStream) {
113            dup2(stdoutOut, 2);
114        } else {
115            dup2(stderrOut, 2);
116        }
117
118        // Close all but statusOut. This saves some work in the next step.
119        closePipes(pipes, statusOut);
120
121        // Make statusOut automatically close if execvp() succeeds.
122        fcntl(statusOut, F_SETFD, FD_CLOEXEC);
123
124        // Close remaining unwanted open fds.
125        closeNonStandardFds(statusOut, androidSystemPropertiesFd);
126
127        // Switch to working directory.
128        if (workingDirectory != NULL) {
129            if (chdir(workingDirectory) == -1) {
130                goto execFailed;
131            }
132        }
133
134        // Set up environment.
135        if (environment != NULL) {
136            extern char** environ; // Standard, but not in any header file.
137            environ = environment;
138        }
139
140        // Execute process. By convention, the first argument in the arg array
141        // should be the command itself. In fact, I get segfaults when this
142        // isn't the case.
143        execvp(commands[0], commands);
144
145        // If we got here, execvp() failed or the working dir was invalid.
146        execFailed:
147            int error = errno;
148            write(statusOut, &error, sizeof(int));
149            close(statusOut);
150            exit(error);
151    }
152
153    // This is the parent process.
154
155    // Close child's pipe ends.
156    close(stdinIn);
157    close(stdoutOut);
158    close(stderrOut);
159    close(statusOut);
160
161    // Check status pipe for an error code. If execvp() succeeds, the other
162    // end of the pipe should automatically close, in which case, we'll read
163    // nothing.
164    int result;
165    int count = read(statusIn, &result, sizeof(int));
166    close(statusIn);
167    if (count > 0) {
168        jniThrowIOException(env, result);
169
170        close(stdoutIn);
171        close(stdinOut);
172        close(stderrIn);
173
174        return -1;
175    }
176
177    // Fill in file descriptor wrappers.
178    jniSetFileDescriptorOfFD(env, inDescriptor, stdoutIn);
179    jniSetFileDescriptorOfFD(env, outDescriptor, stdinOut);
180    jniSetFileDescriptorOfFD(env, errDescriptor, stderrIn);
181
182    return childPid;
183}
184
185/** Converts a Java String[] to a 0-terminated char**. */
186static char** convertStrings(JNIEnv* env, jobjectArray javaArray) {
187    if (javaArray == NULL) {
188        return NULL;
189    }
190
191    jsize length = env->GetArrayLength(javaArray);
192    char** array = new char*[length + 1];
193    array[length] = 0;
194    for (jsize i = 0; i < length; ++i) {
195        ScopedLocalRef<jstring> javaEntry(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, i)));
196        // We need to pass these strings to const-unfriendly code.
197        char* entry = const_cast<char*>(env->GetStringUTFChars(javaEntry.get(), NULL));
198        array[i] = entry;
199    }
200
201    return array;
202}
203
204/** Frees a char** which was converted from a Java String[]. */
205static void freeStrings(JNIEnv* env, jobjectArray javaArray, char** array) {
206    if (javaArray == NULL) {
207        return;
208    }
209
210    jsize length = env->GetArrayLength(javaArray);
211    for (jsize i = 0; i < length; ++i) {
212        ScopedLocalRef<jstring> javaEntry(env, reinterpret_cast<jstring>(env->GetObjectArrayElement(javaArray, i)));
213        env->ReleaseStringUTFChars(javaEntry.get(), array[i]);
214    }
215
216    delete[] array;
217}
218
219/**
220 * Converts Java String[] to char** and delegates to executeProcess().
221 */
222static pid_t ProcessManager_exec(JNIEnv* env, jclass, jobjectArray javaCommands,
223        jobjectArray javaEnvironment, jstring javaWorkingDirectory,
224        jobject inDescriptor, jobject outDescriptor, jobject errDescriptor,
225        jboolean redirectErrorStream) {
226
227    // Copy commands into char*[].
228    char** commands = convertStrings(env, javaCommands);
229
230    // Extract working directory string.
231    const char* workingDirectory = NULL;
232    if (javaWorkingDirectory != NULL) {
233        workingDirectory = env->GetStringUTFChars(javaWorkingDirectory, NULL);
234    }
235
236    // Convert environment array.
237    char** environment = convertStrings(env, javaEnvironment);
238
239    pid_t result = executeProcess(env, commands, environment, workingDirectory,
240            inDescriptor, outDescriptor, errDescriptor, redirectErrorStream);
241
242    // Temporarily clear exception so we can clean up.
243    jthrowable exception = env->ExceptionOccurred();
244    env->ExceptionClear();
245
246    freeStrings(env, javaEnvironment, environment);
247
248    // Clean up working directory string.
249    if (javaWorkingDirectory != NULL) {
250        env->ReleaseStringUTFChars(javaWorkingDirectory, workingDirectory);
251    }
252
253    freeStrings(env, javaCommands, commands);
254
255    // Re-throw exception if present.
256    if (exception != NULL) {
257        if (env->Throw(exception) < 0) {
258            ALOGE("Error rethrowing exception!");
259        }
260    }
261
262    return result;
263}
264
265static JNINativeMethod methods[] = {
266    NATIVE_METHOD(ProcessManager, exec, "([Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Z)I"),
267};
268void register_java_lang_ProcessManager(JNIEnv* env) {
269    jniRegisterNativeMethods(env, "java/lang/ProcessManager", methods, NELEM(methods));
270}
271