By default, the created process does not have its own terminal or console. All its standard I/O (i.e. stdin, stdout, stderr) operations will be redirected to the parent process, where they can be accessed via the streams obtained using the methods {@link #getOutputStream()}, {@link #getInputStream()}, and {@link #getErrorStream()}. The parent process uses these streams to feed input to and get output from the process. Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the process may cause the process to block, or even deadlock.
public Process exec(String prog)throws java.io.IOException { return exec(prog, null, null); } public Process exec(String prog, String[] envp, File directory)throws java.io.IOException { // Sanity checks if (prog == null) { thrownewNullPointerException("prog == null"); } elseif (prog.isEmpty()) { thrownewIllegalArgumentException("prog is empty"); }
// Break down into tokens, as described in Java docs StringTokenizertokenizer=newStringTokenizer(prog); intlength= tokenizer.countTokens(); String[] progArray = newString[length]; for (inti=0; i < length; i++) { progArray[i] = tokenizer.nextToken(); }
// Delegate return exec(progArray, envp, directory); } public Process exec(String[] progArray, String[] envp, File directory)throws IOException { // ProcessManager is responsible for all argument checking. return ProcessManager.getInstance().exec(progArray, envp, directory, false); }
/** * Executes a process and returns an object representing it. */ public Process exec(String[] taintedCommand, String[] taintedEnvironment, File workingDirectory, boolean redirectErrorStream)throws IOException { // Make sure we throw the same exceptions as the RI. if (taintedCommand == null) { thrownewNullPointerException("taintedCommand == null"); } if (taintedCommand.length == 0) { thrownewIndexOutOfBoundsException("taintedCommand.length == 0"); }
// Handle security and safety by copying mutable inputs and checking them. String[] command = taintedCommand.clone(); String[] environment = taintedEnvironment != null ? taintedEnvironment.clone() : null;
// Check we're not passing null Strings to the native exec. for (inti=0; i < command.length; i++) { if (command[i] == null) { thrownewNullPointerException("taintedCommand[" + i + "] == null"); } } // The environment is allowed to be null or empty, but no element may be null. if (environment != null) { for (inti=0; i < environment.length; i++) { if (environment[i] == null) { thrownewNullPointerException("taintedEnvironment[" + i + "] == null"); } } }
// Clean up working directory string. if (javaWorkingDirectory != NULL) { env->ReleaseStringUTFChars(javaWorkingDirectory, workingDirectory); }
return result; }
/** Executes a command in a child process. */ staticpid_tExecuteProcess(JNIEnv* env, char** commands, char** environment, constchar* 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; }
// 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);
// Switch to working directory. if (workingDirectory != NULL) { if (chdir(workingDirectory) == -1) { AbortChild(statusOut); } }
// Set up environment. if (environment != NULL) { externchar** 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. 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);