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.
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));
+}