diff options
author | Elliott Hughes <enh@google.com> | 2009-10-23 18:10:44 -0700 |
---|---|---|
committer | Elliott Hughes <enh@google.com> | 2009-10-23 18:10:44 -0700 |
commit | 94367e0ead84fc2228799a78ec207ea52e203f1a (patch) | |
tree | 78746c1ddf814d69aac118b8789fbf62f477beaf /luni-kernel/src/main/native/java_lang_ProcessManager.cpp | |
parent | 954754fc999fb5fb747f7928302639d16dd3ac7e (diff) | |
download | libcore-94367e0ead84fc2228799a78ec207ea52e203f1a.zip libcore-94367e0ead84fc2228799a78ec207ea52e203f1a.tar.gz libcore-94367e0ead84fc2228799a78ec207ea52e203f1a.tar.bz2 |
Switch ProcessManager and System over to C++.
Bug 2180063 will require changes to ProcessManager, so now's a good time
to make the switch in this directory.
Diffstat (limited to 'luni-kernel/src/main/native/java_lang_ProcessManager.cpp')
-rw-r--r-- | luni-kernel/src/main/native/java_lang_ProcessManager.cpp | 419 |
1 files changed, 419 insertions, 0 deletions
diff --git a/luni-kernel/src/main/native/java_lang_ProcessManager.cpp b/luni-kernel/src/main/native/java_lang_ProcessManager.cpp new file mode 100644 index 0000000..8aa793c --- /dev/null +++ b/luni-kernel/src/main/native/java_lang_ProcessManager.cpp @@ -0,0 +1,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" +#include "AndroidSystemNatives.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 clazz, 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) { + 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); + 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 clazz, jobjectArray javaCommands, + jobjectArray javaEnvironment, jstring javaWorkingDirectory, + jobject inDescriptor, jobject outDescriptor, jobject errDescriptor) { + + // 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); + + // 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[] = { + { "kill", "(I)V", (void*) java_lang_ProcessManager_kill }, + { "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;)I", (void*) java_lang_ProcessManager_exec }, + { "staticInitialize", "()V", + (void*) java_lang_ProcessManager_staticInitialize }, + { "close", "(Ljava/io/FileDescriptor;)V", + (void*) java_lang_ProcessManager_close }, +}; + +int register_java_lang_ProcessManager(JNIEnv* env) { + return jniRegisterNativeMethods( + env, "java/lang/ProcessManager", methods, NELEM(methods)); +} |