diff options
Diffstat (limited to 'luni/src/main')
-rw-r--r-- | luni/src/main/native/java_lang_ProcessManager.cpp | 358 |
1 files changed, 187 insertions, 171 deletions
diff --git a/luni/src/main/native/java_lang_ProcessManager.cpp b/luni/src/main/native/java_lang_ProcessManager.cpp index e3f0f45..783fbd1 100644 --- a/luni/src/main/native/java_lang_ProcessManager.cpp +++ b/luni/src/main/native/java_lang_ProcessManager.cpp @@ -16,223 +16,239 @@ #define LOG_TAG "ProcessManager" -#include <sys/resource.h> -#include <sys/types.h> -#include <unistd.h> +#include <dirent.h> +#include <errno.h> #include <fcntl.h> #include <stdlib.h> #include <string.h> -#include <errno.h> +#include <sys/resource.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <unistd.h> +#include "cutils/log.h" #include "jni.h" -#include "JNIHelp.h" #include "JniConstants.h" +#include "JNIHelp.h" #include "Portability.h" #include "ScopedLocalRef.h" -#include "cutils/log.h" #include "toStringArray.h" -/** 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); - } +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 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]); - } +#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]); } + } } -/** 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); - } +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); +} - // 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; - } +/** 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; + } + 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); } - // 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, 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); - } + // Close all but statusOut. This saves some work in the next step. + ClosePipes(pipes, statusOut); - // This is the parent process. + // Make statusOut automatically close if execvp() succeeds. + fcntl(statusOut, F_SETFD, FD_CLOEXEC); - // Close child's pipe ends. - close(stdinIn); - close(stdoutOut); - close(stderrOut); - close(statusOut); + // Close remaining unwanted open fds. + CloseNonStandardFds(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); + // Switch to working directory. + if (workingDirectory != NULL) { + if (chdir(workingDirectory) == -1) { + AbortChild(statusOut); + } + } - close(stdoutIn); - close(stdinOut); - close(stderrIn); + // Set up environment. + if (environment != NULL) { + extern char** environ; // Standard, but not in any header file. + environ = environment; + } - return -1; + // 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)); } - // Fill in file descriptor wrappers. - jniSetFileDescriptorOfFD(env, inDescriptor, stdoutIn); - jniSetFileDescriptorOfFD(env, outDescriptor, stdinOut); - jniSetFileDescriptorOfFD(env, errDescriptor, stderrIn); + return -1; + } + + // Fill in file descriptor wrappers. + jniSetFileDescriptorOfFD(env, inDescriptor, stdoutIn); + jniSetFileDescriptorOfFD(env, outDescriptor, stdinOut); + jniSetFileDescriptorOfFD(env, errDescriptor, stderrIn); - return childPid; + return childPid; } /** - * Converts Java String[] to char** and delegates to executeProcess(). + * 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) { + jobjectArray javaEnvironment, jstring javaWorkingDirectory, + jobject inDescriptor, jobject outDescriptor, jobject errDescriptor, + jboolean redirectErrorStream) { - // Copy commands into char*[]. - char** commands = convertStrings(env, javaCommands); + // 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); - } + // Extract working directory string. + const char* workingDirectory = NULL; + if (javaWorkingDirectory != NULL) { + workingDirectory = env->GetStringUTFChars(javaWorkingDirectory, NULL); + } - // Convert environment array. - char** environment = convertStrings(env, javaEnvironment); + // Convert environment array. + char** environment = convertStrings(env, javaEnvironment); - pid_t result = executeProcess(env, commands, environment, workingDirectory, - inDescriptor, outDescriptor, errDescriptor, redirectErrorStream); + 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(); + // Temporarily clear exception so we can clean up. + jthrowable exception = env->ExceptionOccurred(); + env->ExceptionClear(); - freeStrings(env, javaEnvironment, environment); + freeStrings(env, javaEnvironment, environment); - // Clean up working directory string. - if (javaWorkingDirectory != NULL) { - env->ReleaseStringUTFChars(javaWorkingDirectory, workingDirectory); - } + // Clean up working directory string. + if (javaWorkingDirectory != NULL) { + env->ReleaseStringUTFChars(javaWorkingDirectory, workingDirectory); + } - freeStrings(env, javaCommands, commands); + freeStrings(env, javaCommands, commands); - // Re-throw exception if present. - if (exception != NULL) { - if (env->Throw(exception) < 0) { - ALOGE("Error rethrowing exception!"); - } + // Re-throw exception if present. + if (exception != NULL) { + if (env->Throw(exception) < 0) { + ALOGE("Error rethrowing exception!"); } + } - return result; + return result; } -static JNINativeMethod methods[] = { - NATIVE_METHOD(ProcessManager, exec, "([Ljava/lang/String;[Ljava/lang/String;Ljava/lang/String;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Ljava/io/FileDescriptor;Z)I"), +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", methods, NELEM(methods)); + jniRegisterNativeMethods(env, "java/lang/ProcessManager", gMethods, NELEM(gMethods)); } |