summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--luni/src/main/native/java_lang_ProcessManager.cpp358
-rw-r--r--luni/src/test/java/tests/api/java/lang/ProcessTest.java265
2 files changed, 283 insertions, 340 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));
}
diff --git a/luni/src/test/java/tests/api/java/lang/ProcessTest.java b/luni/src/test/java/tests/api/java/lang/ProcessTest.java
index f875dc0..b1a331a 100644
--- a/luni/src/test/java/tests/api/java/lang/ProcessTest.java
+++ b/luni/src/test/java/tests/api/java/lang/ProcessTest.java
@@ -17,192 +17,119 @@
package tests.api.java.lang;
-import dalvik.annotation.BrokenTest;
-
+import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
+import java.util.ArrayList;
public class ProcessTest extends junit.framework.TestCase {
- /**
- * java.lang.Process#getInputStream()
- */
- public void test_getInputStream() {
- try {
- // Test for:
- //Object[] execArgs = Support_Exec.execJava2(
- // new String[] { "tests.support.Support_AvailTest" }, null,
- // true);
- //Process proc = (Process) execArgs[0];
-
- String[] commands = { "sleep", "1"};
- Process proc = Runtime.getRuntime().exec(commands, null, null);
-
- OutputStream os = proc.getOutputStream();
-
- // first number indicates total stream length
- // second number indicates length of data after second space
- // this will allow us to verify length at start, middle, and end
- os.write("10 5 abcde".getBytes());
- os.close();
-
- InputStream is = proc.getInputStream();
- StringBuffer msg = new StringBuffer("");
- while (true) {
- int c = is.read();
- if (c == -1)
- break;
- msg.append((char) c);
- }
- is.close();
- proc.waitFor();
- //Support_Exec.checkStderr(execArgs);
- proc.destroy();
- assertEquals("true", msg.toString(), msg.toString());
- } catch (IOException e) {
- fail("IOException executing avail test: " + e);
- } catch (InterruptedException e) {
- fail("InterruptedException executing avail test: " + e);
- }
+ public void test_55017() throws Exception {
+ ArrayList<Process> children = new ArrayList<Process>();
+ for (int i = 0; i < 256; ++i) {
+ try {
+ children.add(Runtime.getRuntime().exec(new String[] { "/system/bin/does-not-exist" }, null, null));
+ System.gc();
+ } catch (IOException expected) {
+ }
}
-
- /**
- * java.lang.Process#getOutputStream()
- */
- public void test_getOutputStream() {
- try {
- String[] commands = { "sleep", "1"};
- Process proc = Runtime.getRuntime().exec(commands, null, null);
- OutputStream os = proc.getOutputStream();
- // send data, and check if it is echoed back correctly
- String str1 = "Some data for testing communication between processes\n";
- String str2 = "More data that serves the same purpose.\n";
- String str3 = "Here is some more data.\n";
- os.write(str1.getBytes());
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- os.close();
- InputStream is = proc.getInputStream();
- StringBuffer msg = new StringBuffer("");
- while (true) {
- int c = is.read();
- if (c == -1)
- break;
- msg.append((char) c);
- }
- is.close();
- proc.waitFor();
- //Support_Exec.checkStderr(execArgs);
- proc.destroy();
- String org = str1;
- String recvd = msg.toString();
- // Doesn't pass on RI
- // assertTrue("Data returned did not match data sent. Received: '"
- // + recvd + "' sent: '" + org + "'", recvd.equals(org));
- } catch (IOException e) {
- fail("IOException executing avail test: " + e);
- } catch (InterruptedException e) {
- fail("InterruptedException executing avail test: " + e);
- }
+ assertEquals(0, children.size());
+
+ boolean onDevice = new File("/system/bin").exists();
+ String[] psCommand = onDevice ? new String[] { "ps" } : new String[] { "ps", "s" };
+ Process ps = Runtime.getRuntime().exec(psCommand, null, null);
+ int zombieCount = 0;
+ for (String line : readAndCloseStream(ps.getInputStream()).split("\n")) {
+ if (line.contains(" Z ") || line.contains(" Z+ ")) {
+ ++zombieCount;
+ }
}
-
- public void test_exitValue() {
- try {
- String[] commands = { "ls" };
- Process process = Runtime.getRuntime().exec(commands, null, null);
- try {
- Thread.sleep(5000);
- } catch(Exception e) {
-
- }
- assertTrue(process.exitValue() == 0);
-
- String[] commandsSleep = { "sleep", "3" };
- process = Runtime.getRuntime().exec(commandsSleep, null, null);
- process.destroy();
- try {
- Thread.sleep(5000);
- } catch (Exception e) {}
- assertTrue(process.exitValue() != 0);
-
- process = Runtime.getRuntime().exec(commandsSleep, null, null);
- try {
- process.exitValue();
- fail("IllegalThreadStateException was not thrown.");
- } catch(IllegalThreadStateException itse) {
- //expected
- }
- } catch (IOException e) {
- fail("IOException was thrown.");
- }
+ assertEquals(0, zombieCount);
+ }
+
+ public void test_getOutputStream() throws Exception {
+ String[] commands = { "cat", "-"};
+ Process p = Runtime.getRuntime().exec(commands, null, null);
+ OutputStream os = p.getOutputStream();
+ // send data, and check if it is echoed back correctly
+ String str1 = "Some data for testing communication between processes\n";
+ String str2 = "More data that serves the same purpose.\n";
+ String str3 = "Here is some more data.\n";
+ os.write(str1.getBytes());
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
}
+ os.write(str2.getBytes());
+ os.write(str3.getBytes());
+ os.close();
- public void test_Constructor() {
- ProcessClass pc = new ProcessClass();
- assertTrue(pc.exitValue() == 0);
- }
+ String received = readAndCloseStream(p.getInputStream());
+ assertEquals(str1 + str2 + str3, received);
- @BrokenTest("Sporadic timeouts in CTS, but not in CoreTestRunner")
- public void test_destroy() {
- String[] commands = { "ls"};
- try {
- Process process = Runtime.getRuntime().exec(commands, null, null);
- process.destroy();
- } catch (IOException e) {
- fail("IOException was thrown.");
- }
- }
+ String stderr = readAndCloseStream(p.getErrorStream());
+ assertEquals("", stderr);
- protected void setUp() {
- }
+ p.waitFor();
+ p.destroy();
+ }
- protected void tearDown() {
- }
+ public void test_getErrorStream() throws Exception {
+ String[] commands = { "cat", "--no-such-option"};
+ Process p = Runtime.getRuntime().exec(commands, null, null);
- protected void doneSuite() {
- }
+ p.getOutputStream().close();
- class ProcessClass extends Process {
+ String received = readAndCloseStream(p.getInputStream());
+ assertEquals("", received);
- ProcessClass() {
- super();
- }
+ String stderr = readAndCloseStream(p.getErrorStream());
+ assertTrue(stderr, stderr.contains("unrecognized option") || stderr.contains("unknown option"));
- @Override
- public void destroy() {
-
- }
-
- @Override
- public int exitValue() {
- // TODO Auto-generated method stub
- return 0;
- }
-
- @Override
- public InputStream getErrorStream() {
- return null;
- }
-
- @Override
- public InputStream getInputStream() {
- return null;
- }
-
- @Override
- public OutputStream getOutputStream() {
- return null;
- }
-
- @Override
- public int waitFor() throws InterruptedException {
- // TODO Auto-generated method stub
- return 0;
- }
+ p.waitFor();
+ p.destroy();
+ }
+ private String readAndCloseStream(InputStream is) throws IOException {
+ StringBuffer result = new StringBuffer();
+ while (true) {
+ int c = is.read();
+ if (c == -1) {
+ break;
+ }
+ result.append((char) c);
+ }
+ is.close();
+ return result.toString();
+ }
+
+ public void test_exitValue() throws Exception {
+ String[] commands = { "ls" };
+ Process process = Runtime.getRuntime().exec(commands, null, null);
+ process.waitFor();
+ assertEquals(0, process.exitValue());
+
+ String[] commandsSleep = { "sleep", "3000" };
+ process = Runtime.getRuntime().exec(commandsSleep, null, null);
+ process.destroy();
+ process.waitFor(); // destroy is asynchronous.
+ assertTrue(process.exitValue() != 0);
+
+ process = Runtime.getRuntime().exec(new String[] { "sleep", "3000" }, null, null);
+ try {
+ process.exitValue();
+ fail();
+ } catch(IllegalThreadStateException expected) {
}
+ }
+
+ public void test_destroy() throws Exception {
+ String[] commands = { "ls"};
+ Process process = Runtime.getRuntime().exec(commands, null, null);
+ process.destroy();
+ process.destroy();
+ process.destroy();
+ }
}