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