diff options
Diffstat (limited to 'luni-kernel/src/main/native')
-rw-r--r-- | luni-kernel/src/main/native/java_lang_ProcessManager.c | 438 | ||||
-rw-r--r-- | luni-kernel/src/main/native/java_lang_System.c | 106 | ||||
-rw-r--r-- | luni-kernel/src/main/native/sub.mk | 17 |
3 files changed, 561 insertions, 0 deletions
diff --git a/luni-kernel/src/main/native/java_lang_ProcessManager.c b/luni-kernel/src/main/native/java_lang_ProcessManager.c new file mode 100644 index 0000000..ee2fc58 --- /dev/null +++ b/luni-kernel/src/main/native/java_lang_ProcessManager.c @@ -0,0 +1,438 @@ +/* + * 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/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 <dirent.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 int closeNow(int fd) { + int result; + do { + result = close(fd); + } while (result == -1 && errno == EINTR); + return result; +} + +/** Closes a file descriptor. */ +static void java_lang_ProcessManager_close(JNIEnv* env, + jclass clazz, jobject javaDescriptor) { + int fd = (*env)->GetIntField(env, javaDescriptor, descriptorField); + if (closeNow(fd) == -1) { + jclass ioException = (*env)->FindClass(env, "java/io/IOException"); + (*env)->ThrowNew(env, ioException, strerror(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; + + pid_t pid = wait(&status); + + 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(env, o, onExitMethod, pid, status); + + if ((*env)->ExceptionOccurred(env)) { + /* + * 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). */ +static void closeNonStandardFds(int skipFd) { + DIR* dir = opendir("/proc/self/fd"); + + if (dir == NULL) { + // Print message to standard err. The parent process can read this + // from Process.getErrorStream(). + perror("opendir"); + return; + } + + struct dirent* entry; + int dirFd = dirfd(dir); + while ((entry = readdir(dir)) != NULL) { + int fd = atoi(entry->d_name); + if (fd > 2 && fd != dirFd && fd != skipFd +#ifdef ANDROID + && fd != androidSystemPropertiesFd +#endif + ) { + close(fd); + } + } + + closedir(dir); +} + +#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) { + // 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(env, javaArray); + array = (char**) malloc(sizeof(char*) * (length + 1)); + array[length] = 0; + jsize index; + for (index = 0; index < length; index++) { + jstring javaEntry = (jstring) (*env)->GetObjectArrayElement( + env, javaArray, index); + char* entry = (char*) (*env)->GetStringUTFChars( + env, 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(env, javaArray); + jsize index; + for (index = 0; index < length; index++) { + jstring javaEntry = (jstring) (*env)->GetObjectArrayElement( + env, javaArray, index); + (*env)->ReleaseStringUTFChars(env, 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 = (const char*) (*env)->GetStringUTFChars( + env, 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); + (*env)->ExceptionClear(env); + + freeStrings(env, javaEnvironment, environment); + + // Clean up working directory string. + if (javaWorkingDirectory != NULL) { + (*env)->ReleaseStringUTFChars( + env, javaWorkingDirectory, workingDirectory); + } + + freeStrings(env, javaCommands, commands); + + // Re-throw exception if present. + if (exception != NULL) { + if ((*env)->Throw(env, 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(env, clazz, "onExit", "(II)V"); + if (onExitMethod == NULL) { + return; + } + + jclass fileDescriptorClass + = (*env)->FindClass(env, "java/io/FileDescriptor"); + if (fileDescriptorClass == NULL) { + return; + } + descriptorField = (*env)->GetFieldID(env, 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)); +} diff --git a/luni-kernel/src/main/native/java_lang_System.c b/luni-kernel/src/main/native/java_lang_System.c new file mode 100644 index 0000000..1c06a40 --- /dev/null +++ b/luni-kernel/src/main/native/java_lang_System.c @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008 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. + */ + +#include "JNIHelp.h" + +#include <stdlib.h> +#include <string.h> + + +/* + * public static native String getEnvByName(String name) + * + * (Calling it plain "getenv" might confuse GDB if you try to put a breakpoint + * on the libc version.) + */ +static jstring java_getEnvByName(JNIEnv* env, jclass clazz, jstring nameStr) +{ + jstring valueStr = NULL; + + if (nameStr != NULL) { + const char* name; + const char* val; + + name = (*env)->GetStringUTFChars(env, nameStr, NULL); + val = getenv(name); + if (val != NULL) + valueStr = (*env)->NewStringUTF(env, val); + + (*env)->ReleaseStringUTFChars(env, nameStr, name); + } else { + jniThrowException(env, "java/lang/NullPointerException", NULL); + } + + return valueStr; +} + +/* + * Pointer to complete environment, from Posix. + */ +extern char** environ; + +/* + * public static native String getEnvByIndex() + * + * (Calling it plain "getenv" might confuse GDB if you try to put a breakpoint + * on the libc version.) + */ +static jstring java_getEnvByIndex(JNIEnv* env, jclass clazz, jint index) +{ + jstring valueStr = NULL; + + char* entry = environ[index]; + if (entry != NULL) { + valueStr = (*env)->NewStringUTF(env, entry); + } + + return valueStr; +} + +/* + * public static native String setFieldImpl() + * + * Sets a field via JNI. Used for the standard streams, which are r/o + * otherwise. + */ +static void java_setFieldImpl(JNIEnv* env, jclass clazz, jstring name, jstring sig, jobject object) +{ + const char* fieldName = (*env)->GetStringUTFChars(env, name, NULL); + const char* fieldSig = (*env)->GetStringUTFChars(env, sig, NULL); + + jfieldID fieldID = (*env)->GetStaticFieldID(env, clazz, fieldName, fieldSig); + (*env)->SetStaticObjectField(env, clazz, fieldID, object); + + (*env)->ReleaseStringUTFChars(env, name, fieldName); + (*env)->ReleaseStringUTFChars(env, sig, fieldSig); +} + +/* + * JNI registration + */ +static JNINativeMethod gMethods[] = { + /* name, signature, funcPtr */ + { "getEnvByName", "(Ljava/lang/String;)Ljava/lang/String;", java_getEnvByName }, + { "getEnvByIndex", "(I)Ljava/lang/String;", java_getEnvByIndex }, + { "setFieldImpl", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/Object;)V", java_setFieldImpl }, +}; + +int register_java_lang_System(JNIEnv* env) +{ + return jniRegisterNativeMethods(env, "java/lang/System", + gMethods, NELEM(gMethods)); +} + diff --git a/luni-kernel/src/main/native/sub.mk b/luni-kernel/src/main/native/sub.mk new file mode 100644 index 0000000..cd4ce92 --- /dev/null +++ b/luni-kernel/src/main/native/sub.mk @@ -0,0 +1,17 @@ +# This file is included by the top-level libcore Android.mk. +# It's not a normal makefile, so we don't include CLEAR_VARS +# or BUILD_*_LIBRARY. + +LOCAL_SRC_FILES := \ + java_lang_ProcessManager.c \ + java_lang_System.c + +LOCAL_C_INCLUDES += + +# Any shared/static libs that are listed here must also +# be listed in libs/nativehelper/Android.mk. +# TODO: fix this requirement + +LOCAL_SHARED_LIBRARIES += + +LOCAL_STATIC_LIBRARIES += |