/* * 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 <dirent.h> #include <errno.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> #include <sys/resource.h> #include <sys/types.h> #include <sys/wait.h> #include <unistd.h> #include "cutils/log.h" #include "jni.h" #include "ExecStrings.h" #include "JNIHelp.h" #include "JniConstants.h" #include "Portability.h" #include "ScopedLocalRef.h" #include "toStringArray.h" static void CloseNonStandardFds(int status_pipe_fd) { // On Cygwin, Linux, and Solaris, the best way to close iterates over "/proc/self/fd/". const char* fd_path = "/proc/self/fd"; #ifdef __APPLE__ // On Mac OS, there's "/dev/fd/" which Linux seems to link to "/proc/self/fd/", // but which on Solaris appears to be something quite different. fd_path = "/dev/fd"; #endif // Keep track of the system properties fd so we don't close it. int properties_fd = -1; char* properties_fd_string = getenv("ANDROID_PROPERTY_WORKSPACE"); if (properties_fd_string != NULL) { properties_fd = atoi(properties_fd_string); } DIR* d = opendir(fd_path); int dir_fd = dirfd(d); dirent* e; while ((e = readdir(d)) != NULL) { char* end; int fd = strtol(e->d_name, &end, 10); if (!*end) { if (fd > STDERR_FILENO && fd != dir_fd && fd != status_pipe_fd && fd != properties_fd) { close(fd); } } } closedir(d); } #define PIPE_COUNT 4 // Number of pipes used to communicate with child. static void ClosePipes(int pipes[], int skip_fd) { for (int i = 0; i < PIPE_COUNT * 2; i++) { int fd = pipes[i]; if (fd != -1 && fd != skip_fd) { close(pipes[i]); } } } static void AbortChild(int status_pipe_fd) { int error = errno; TEMP_FAILURE_RETRY(write(status_pipe_fd, &error, sizeof(int))); close(status_pipe_fd); _exit(127); } /** 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) { // 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(3) or free(3) after this point! // A thread in the parent that no longer exists in the child may have held the heap lock // when we forked, so an attempt to malloc(3) or free(3) 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); // Switch to working directory. if (workingDirectory != NULL) { if (chdir(workingDirectory) == -1) { AbortChild(statusOut); } } // 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. execvp(commands[0], commands); AbortChild(statusOut); } // 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(2) succeeds, the other // end of the pipe should automatically close, in which case, we'll read // nothing. int child_errno; ssize_t count = TEMP_FAILURE_RETRY(read(statusIn, &child_errno, sizeof(int))); close(statusIn); if (count > 0) { // chdir(2) or execvp(2) in the child failed. // TODO: track which so we can be more specific in the detail message. jniThrowIOException(env, child_errno); close(stdoutIn); close(stdinOut); close(stderrIn); // Reap our zombie child right away. int status; int rc = TEMP_FAILURE_RETRY(waitpid(childPid, &status, 0)); if (rc == -1) { ALOGW("waitpid on failed exec failed: %s", strerror(errno)); } return -1; } // Fill in file descriptor wrappers. jniSetFileDescriptorOfFD(env, inDescriptor, stdoutIn); jniSetFileDescriptorOfFD(env, outDescriptor, stdinOut); jniSetFileDescriptorOfFD(env, errDescriptor, stderrIn); return childPid; } /** * 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) { ExecStrings commands(env, javaCommands); ExecStrings environment(env, javaEnvironment); // Extract working directory string. const char* workingDirectory = NULL; if (javaWorkingDirectory != NULL) { workingDirectory = env->GetStringUTFChars(javaWorkingDirectory, NULL); } pid_t result = ExecuteProcess(env, commands.get(), environment.get(), workingDirectory, inDescriptor, outDescriptor, errDescriptor, redirectErrorStream); // Clean up working directory string. if (javaWorkingDirectory != NULL) { env->ReleaseStringUTFChars(javaWorkingDirectory, workingDirectory); } return result; } static JNINativeMethod gMethods[] = { NATIVE_METHOD(ProcessManager, exec, "([Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Z)I"), }; void register_java_lang_ProcessManager(JNIEnv* env) { jniRegisterNativeMethods(env, "java/lang/ProcessManager", gMethods, NELEM(gMethods)); }