4960438: (process) Need IO redirection API for subprocesses

Reviewed-by: alanb, iris
This commit is contained in:
Martin Buchholz 2008-03-10 14:32:51 -07:00
parent 504a24907d
commit abde1241e1
12 changed files with 1666 additions and 462 deletions

View File

@ -41,18 +41,24 @@ import java.io.*;
* <p>The methods that create processes may not work well for special * <p>The methods that create processes may not work well for special
* processes on certain native platforms, such as native windowing * processes on certain native platforms, such as native windowing
* processes, daemon processes, Win16/DOS processes on Microsoft * processes, daemon processes, Win16/DOS processes on Microsoft
* Windows, or shell scripts. The created subprocess does not have * Windows, or shell scripts.
* its own terminal or console. All its standard I/O (i.e. stdin, *
* stdout, stderr) operations will be redirected to the parent process * <p>By default, the created subprocess does not have its own terminal
* through three streams * or console. All its standard I/O (i.e. stdin, stdout, stderr)
* ({@link #getOutputStream()}, * operations will be redirected to the parent process, where they can
* {@link #getInputStream()}, * be accessed via the streams obtained using the methods
* {@link #getErrorStream()}). * {@link #getOutputStream()},
* {@link #getInputStream()}, and
* {@link #getErrorStream()}.
* The parent process uses these streams to feed input to and get output * The parent process uses these streams to feed input to and get output
* from the subprocess. Because some native platforms only provide * from the subprocess. Because some native platforms only provide
* limited buffer size for standard input and output streams, failure * limited buffer size for standard input and output streams, failure
* to promptly write the input stream or read the output stream of * to promptly write the input stream or read the output stream of
* the subprocess may cause the subprocess to block, and even deadlock. * the subprocess may cause the subprocess to block, or even deadlock.
*
* <p>Where desired, <a href="ProcessBuilder.html#redirect-input">
* subprocess I/O can also be redirected</a>
* using methods of the {@link ProcessBuilder} class.
* *
* <p>The subprocess is not killed when there are no more references to * <p>The subprocess is not killed when there are no more references to
* the {@code Process} object, but rather the subprocess * the {@code Process} object, but rather the subprocess
@ -62,16 +68,22 @@ import java.io.*;
* Process} object execute asynchronously or concurrently with respect * Process} object execute asynchronously or concurrently with respect
* to the Java process that owns the {@code Process} object. * to the Java process that owns the {@code Process} object.
* *
* @author unascribed * <p>As of 1.5, {@link ProcessBuilder#start()} is the preferred way
* @see ProcessBuilder * to create a {@code Process}.
*
* @since JDK1.0 * @since JDK1.0
*/ */
public abstract class Process { public abstract class Process {
/** /**
* Returns the output stream connected to the normal input of the * Returns the output stream connected to the normal input of the
* subprocess. Output to the stream is piped into the standard * subprocess. Output to the stream is piped into the standard
* input stream of the process represented by this {@code Process} * input of the process represented by this {@code Process} object.
* object. *
* <p>If the standard input of the subprocess has been redirected using
* {@link ProcessBuilder#redirectInput(Redirect)
* ProcessBuilder.redirectInput}
* then this method will return a
* <a href="ProcessBuilder.html#redirect-input">null output stream</a>.
* *
* <p>Implementation note: It is a good idea for the returned * <p>Implementation note: It is a good idea for the returned
* output stream to be buffered. * output stream to be buffered.
@ -84,30 +96,47 @@ public abstract class Process {
/** /**
* Returns the input stream connected to the normal output of the * Returns the input stream connected to the normal output of the
* subprocess. The stream obtains data piped from the standard * subprocess. The stream obtains data piped from the standard
* output stream of the process represented by this {@code * output of the process represented by this {@code Process} object.
* Process} object. *
* <p>If the standard output of the subprocess has been redirected using
* {@link ProcessBuilder#redirectOutput(Redirect)
* ProcessBuilder.redirectOutput}
* then this method will return a
* <a href="ProcessBuilder.html#redirect-output">null input stream</a>.
*
* <p>Otherwise, if the standard error of the subprocess has been
* redirected using
* {@link ProcessBuilder#redirectErrorStream(boolean)
* ProcessBuilder.redirectErrorStream}
* then the input stream returned by this method will receive the
* merged standard output and the standard error of the subprocess.
* *
* <p>Implementation note: It is a good idea for the returned * <p>Implementation note: It is a good idea for the returned
* input stream to be buffered. * input stream to be buffered.
* *
* @return the input stream connected to the normal output of the * @return the input stream connected to the normal output of the
* subprocess * subprocess
* @see ProcessBuilder#redirectErrorStream()
*/ */
abstract public InputStream getInputStream(); abstract public InputStream getInputStream();
/** /**
* Returns the input stream connected to the error output stream of * Returns the input stream connected to the error output of the
* the subprocess. The stream obtains data piped from the error * subprocess. The stream obtains data piped from the error output
* output stream of the process represented by this {@code Process} * of the process represented by this {@code Process} object.
* object. *
* <p>If the standard error of the subprocess has been redirected using
* {@link ProcessBuilder#redirectError(Redirect)
* ProcessBuilder.redirectError} or
* {@link ProcessBuilder#redirectErrorStream(boolean)
* ProcessBuilder.redirectErrorStream}
* then this method will return a
* <a href="ProcessBuilder.html#redirect-output">null input stream</a>.
* *
* <p>Implementation note: It is a good idea for the returned * <p>Implementation note: It is a good idea for the returned
* input stream to be buffered. * input stream to be buffered.
* *
* @return the input stream connected to the error output stream of * @return the input stream connected to the error output of
* the subprocess * the subprocess
* @see ProcessBuilder#redirectErrorStream()
*/ */
abstract public InputStream getErrorStream(); abstract public InputStream getErrorStream();

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved. * Copyright 2003-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -27,6 +27,10 @@ package java.lang;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileOutputStream;
import java.util.Arrays;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -34,7 +38,7 @@ import java.util.Map;
/** /**
* This class is used to create operating system processes. * This class is used to create operating system processes.
* *
* <p>Each <code>ProcessBuilder</code> instance manages a collection * <p>Each {@code ProcessBuilder} instance manages a collection
* of process attributes. The {@link #start()} method creates a new * of process attributes. The {@link #start()} method creates a new
* {@link Process} instance with those attributes. The {@link * {@link Process} instance with those attributes. The {@link
* #start()} method can be invoked repeatedly from the same instance * #start()} method can be invoked repeatedly from the same instance
@ -59,19 +63,64 @@ import java.util.Map;
* *
* <li>a <i>working directory</i>. The default value is the current * <li>a <i>working directory</i>. The default value is the current
* working directory of the current process, usually the directory * working directory of the current process, usually the directory
* named by the system property <code>user.dir</code>. * named by the system property {@code user.dir}.
*
* <li><a name="redirect-input">a source of <i>standard input</i>.
* By default, the subprocess reads input from a pipe. Java code
* can access this pipe via the output stream returned by
* {@link Process#getOutputStream()}. However, standard input may
* be redirected to another source using
* {@link #redirectInput(Redirect) redirectInput}.
* In this case, {@link Process#getOutputStream()} will return a
* <i>null output stream</i>, for which:
*
* <ul>
* <li>the {@link OutputStream#write(int) write} methods always
* throw {@code IOException}
* <li>the {@link OutputStream#close() close} method does nothing
* </ul>
*
* <li><a name="redirect-output">a destination for <i>standard output</i>
* and <i>standard error</i>. By default, the subprocess writes standard
* output and standard error to pipes. Java code can access these pipes
* via the input streams returned by {@link Process#getInputStream()} and
* {@link Process#getErrorStream()}. However, standard output and
* standard error may be redirected to other destinations using
* {@link #redirectOutput(Redirect) redirectOutput} and
* {@link #redirectError(Redirect) redirectError}.
* In this case, {@link Process#getInputStream()} and/or
* {@link Process#getErrorStream()} will return a <i>null input
* stream</i>, for which:
*
* <ul>
* <li>the {@link InputStream#read() read} methods always return
* {@code -1}
* <li>the {@link InputStream#available() available} method always returns
* {@code 0}
* <li>the {@link InputStream#close() close} method does nothing
* </ul>
* *
* <li>a <i>redirectErrorStream</i> property. Initially, this property * <li>a <i>redirectErrorStream</i> property. Initially, this property
* is <code>false</code>, meaning that the standard output and error * is {@code false}, meaning that the standard output and error
* output of a subprocess are sent to two separate streams, which can * output of a subprocess are sent to two separate streams, which can
* be accessed using the {@link Process#getInputStream()} and {@link * be accessed using the {@link Process#getInputStream()} and {@link
* Process#getErrorStream()} methods. If the value is set to * Process#getErrorStream()} methods.
* <code>true</code>, the standard error is merged with the standard *
* output. This makes it easier to correlate error messages with the * <p>If the value is set to {@code true}, then:
* corresponding output. In this case, the merged data can be read *
* from the stream returned by {@link Process#getInputStream()}, while * <ul>
* reading from the stream returned by {@link * <li>standard error is merged with the standard output and always sent
* Process#getErrorStream()} will get an immediate end of file. * to the same destination (this makes it easier to correlate error
* messages with the corresponding output)
* <li>the common destination of standard error and standard output can be
* redirected using
* {@link #redirectOutput(Redirect) redirectOutput}
* <li>any redirection set by the
* {@link #redirectError(Redirect) redirectError}
* method is ignored when creating a subprocess
* <li>the stream returned from {@link Process#getErrorStream()} will
* always be a <a href="#redirect-output">null input stream</a>
* </ul>
* *
* </ul> * </ul>
* *
@ -87,34 +136,43 @@ import java.util.Map;
* is invoked. * is invoked.
* *
* <p><strong>Note that this class is not synchronized.</strong> * <p><strong>Note that this class is not synchronized.</strong>
* If multiple threads access a <code>ProcessBuilder</code> instance * If multiple threads access a {@code ProcessBuilder} instance
* concurrently, and at least one of the threads modifies one of the * concurrently, and at least one of the threads modifies one of the
* attributes structurally, it <i>must</i> be synchronized externally. * attributes structurally, it <i>must</i> be synchronized externally.
* *
* <p>Starting a new process which uses the default working directory * <p>Starting a new process which uses the default working directory
* and environment is easy: * and environment is easy:
* *
* <blockquote><pre> * <pre> {@code
* Process p = new ProcessBuilder("myCommand", "myArg").start(); * Process p = new ProcessBuilder("myCommand", "myArg").start();
* </pre></blockquote> * }</pre>
* *
* <p>Here is an example that starts a process with a modified working * <p>Here is an example that starts a process with a modified working
* directory and environment: * directory and environment, and redirects standard output and error
* to be appended to a log file:
* *
* <blockquote><pre> * <pre> {@code
* ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2"); * ProcessBuilder pb =
* Map&lt;String, String&gt; env = pb.environment(); * new ProcessBuilder("myCommand", "myArg1", "myArg2");
* Map<String, String> env = pb.environment();
* env.put("VAR1", "myValue"); * env.put("VAR1", "myValue");
* env.remove("OTHERVAR"); * env.remove("OTHERVAR");
* env.put("VAR2", env.get("VAR1") + "suffix"); * env.put("VAR2", env.get("VAR1") + "suffix");
* pb.directory(new File("myDir")); * pb.directory(new File("myDir"));
* File log = new File("log");
* pb.redirectErrorStream(true);
* pb.redirectOutput(Redirect.appendTo(log));
* Process p = pb.start(); * Process p = pb.start();
* </pre></blockquote> * assert pb.redirectInput() == Redirect.PIPE;
* assert pb.redirectOutput().file() == log;
* assert p.getInputStream().read() == -1;
* }</pre>
* *
* <p>To start a process with an explicit set of environment * <p>To start a process with an explicit set of environment
* variables, first call {@link java.util.Map#clear() Map.clear()} * variables, first call {@link java.util.Map#clear() Map.clear()}
* before adding environment variables. * before adding environment variables.
* *
* @author Martin Buchholz
* @since 1.5 * @since 1.5
*/ */
@ -124,20 +182,19 @@ public final class ProcessBuilder
private File directory; private File directory;
private Map<String,String> environment; private Map<String,String> environment;
private boolean redirectErrorStream; private boolean redirectErrorStream;
private Redirect[] redirects;
/** /**
* Constructs a process builder with the specified operating * Constructs a process builder with the specified operating
* system program and arguments. This constructor does <i>not</i> * system program and arguments. This constructor does <i>not</i>
* make a copy of the <code>command</code> list. Subsequent * make a copy of the {@code command} list. Subsequent
* updates to the list will be reflected in the state of the * updates to the list will be reflected in the state of the
* process builder. It is not checked whether * process builder. It is not checked whether
* <code>command</code> corresponds to a valid operating system * {@code command} corresponds to a valid operating system
* command.</p> * command.
* *
* @param command The list containing the program and its arguments * @param command the list containing the program and its arguments
* * @throws NullPointerException if the argument is null
* @throws NullPointerException
* If the argument is <code>null</code>
*/ */
public ProcessBuilder(List<String> command) { public ProcessBuilder(List<String> command) {
if (command == null) if (command == null)
@ -149,12 +206,12 @@ public final class ProcessBuilder
* Constructs a process builder with the specified operating * Constructs a process builder with the specified operating
* system program and arguments. This is a convenience * system program and arguments. This is a convenience
* constructor that sets the process builder's command to a string * constructor that sets the process builder's command to a string
* list containing the same strings as the <code>command</code> * list containing the same strings as the {@code command}
* array, in the same order. It is not checked whether * array, in the same order. It is not checked whether
* <code>command</code> corresponds to a valid operating system * {@code command} corresponds to a valid operating system
* command.</p> * command.
* *
* @param command A string array containing the program and its arguments * @param command a string array containing the program and its arguments
*/ */
public ProcessBuilder(String... command) { public ProcessBuilder(String... command) {
this.command = new ArrayList<String>(command.length); this.command = new ArrayList<String>(command.length);
@ -165,16 +222,15 @@ public final class ProcessBuilder
/** /**
* Sets this process builder's operating system program and * Sets this process builder's operating system program and
* arguments. This method does <i>not</i> make a copy of the * arguments. This method does <i>not</i> make a copy of the
* <code>command</code> list. Subsequent updates to the list will * {@code command} list. Subsequent updates to the list will
* be reflected in the state of the process builder. It is not * be reflected in the state of the process builder. It is not
* checked whether <code>command</code> corresponds to a valid * checked whether {@code command} corresponds to a valid
* operating system command.</p> * operating system command.
* *
* @param command The list containing the program and its arguments * @param command the list containing the program and its arguments
* @return This process builder * @return this process builder
* *
* @throws NullPointerException * @throws NullPointerException if the argument is null
* If the argument is <code>null</code>
*/ */
public ProcessBuilder command(List<String> command) { public ProcessBuilder command(List<String> command) {
if (command == null) if (command == null)
@ -187,12 +243,12 @@ public final class ProcessBuilder
* Sets this process builder's operating system program and * Sets this process builder's operating system program and
* arguments. This is a convenience method that sets the command * arguments. This is a convenience method that sets the command
* to a string list containing the same strings as the * to a string list containing the same strings as the
* <code>command</code> array, in the same order. It is not * {@code command} array, in the same order. It is not
* checked whether <code>command</code> corresponds to a valid * checked whether {@code command} corresponds to a valid
* operating system command.</p> * operating system command.
* *
* @param command A string array containing the program and its arguments * @param command a string array containing the program and its arguments
* @return This process builder * @return this process builder
*/ */
public ProcessBuilder command(String... command) { public ProcessBuilder command(String... command) {
this.command = new ArrayList<String>(command.length); this.command = new ArrayList<String>(command.length);
@ -205,9 +261,9 @@ public final class ProcessBuilder
* Returns this process builder's operating system program and * Returns this process builder's operating system program and
* arguments. The returned list is <i>not</i> a copy. Subsequent * arguments. The returned list is <i>not</i> a copy. Subsequent
* updates to the list will be reflected in the state of this * updates to the list will be reflected in the state of this
* process builder.</p> * process builder.
* *
* @return This process builder's program and its arguments * @return this process builder's program and its arguments
*/ */
public List<String> command() { public List<String> command() {
return command; return command;
@ -225,10 +281,10 @@ public final class ProcessBuilder
* <p>The returned object may be modified using ordinary {@link * <p>The returned object may be modified using ordinary {@link
* java.util.Map Map} operations. These modifications will be * java.util.Map Map} operations. These modifications will be
* visible to subprocesses started via the {@link #start()} * visible to subprocesses started via the {@link #start()}
* method. Two <code>ProcessBuilder</code> instances always * method. Two {@code ProcessBuilder} instances always
* contain independent process environments, so changes to the * contain independent process environments, so changes to the
* returned map will never be reflected in any other * returned map will never be reflected in any other
* <code>ProcessBuilder</code> instance or the values returned by * {@code ProcessBuilder} instance or the values returned by
* {@link System#getenv System.getenv}. * {@link System#getenv System.getenv}.
* *
* <p>If the system does not support environment variables, an * <p>If the system does not support environment variables, an
@ -262,25 +318,24 @@ public final class ProcessBuilder
* <p>The returned map is typically case-sensitive on all platforms. * <p>The returned map is typically case-sensitive on all platforms.
* *
* <p>If a security manager exists, its * <p>If a security manager exists, its
* {@link SecurityManager#checkPermission checkPermission} * {@link SecurityManager#checkPermission checkPermission} method
* method is called with a * is called with a
* <code>{@link RuntimePermission}("getenv.*")</code> * {@link RuntimePermission}{@code ("getenv.*")} permission.
* permission. This may result in a {@link SecurityException} being * This may result in a {@link SecurityException} being thrown.
* thrown.
* *
* <p>When passing information to a Java subprocess, * <p>When passing information to a Java subprocess,
* <a href=System.html#EnvironmentVSSystemProperties>system properties</a> * <a href=System.html#EnvironmentVSSystemProperties>system properties</a>
* are generally preferred over environment variables.</p> * are generally preferred over environment variables.
* *
* @return This process builder's environment * @return this process builder's environment
* *
* @throws SecurityException * @throws SecurityException
* If a security manager exists and its * if a security manager exists and its
* {@link SecurityManager#checkPermission checkPermission} * {@link SecurityManager#checkPermission checkPermission}
* method doesn't allow access to the process environment * method doesn't allow access to the process environment
* *
* @see Runtime#exec(String[],String[],java.io.File) * @see Runtime#exec(String[],String[],java.io.File)
* @see System#getenv() * @see System#getenv()
*/ */
public Map<String,String> environment() { public Map<String,String> environment() {
SecurityManager security = System.getSecurityManager(); SecurityManager security = System.getSecurityManager();
@ -328,12 +383,12 @@ public final class ProcessBuilder
* *
* Subprocesses subsequently started by this object's {@link * Subprocesses subsequently started by this object's {@link
* #start()} method will use this as their working directory. * #start()} method will use this as their working directory.
* The returned value may be <code>null</code> -- this means to use * The returned value may be {@code null} -- this means to use
* the working directory of the current Java process, usually the * the working directory of the current Java process, usually the
* directory named by the system property <code>user.dir</code>, * directory named by the system property {@code user.dir},
* as the working directory of the child process.</p> * as the working directory of the child process.
* *
* @return This process builder's working directory * @return this process builder's working directory
*/ */
public File directory() { public File directory() {
return directory; return directory;
@ -344,50 +399,522 @@ public final class ProcessBuilder
* *
* Subprocesses subsequently started by this object's {@link * Subprocesses subsequently started by this object's {@link
* #start()} method will use this as their working directory. * #start()} method will use this as their working directory.
* The argument may be <code>null</code> -- this means to use the * The argument may be {@code null} -- this means to use the
* working directory of the current Java process, usually the * working directory of the current Java process, usually the
* directory named by the system property <code>user.dir</code>, * directory named by the system property {@code user.dir},
* as the working directory of the child process.</p> * as the working directory of the child process.
* *
* @param directory The new working directory * @param directory the new working directory
* @return This process builder * @return this process builder
*/ */
public ProcessBuilder directory(File directory) { public ProcessBuilder directory(File directory) {
this.directory = directory; this.directory = directory;
return this; return this;
} }
// ---------------- I/O Redirection ----------------
/**
* Implements a <a href="#redirect-output">null input stream</a>.
*/
static class NullInputStream extends InputStream {
public int read() { return -1; }
public int available() { return 0; }
}
/**
* Implements a <a href="#redirect-input">null output stream</a>.
*/
static class NullOutputStream extends OutputStream {
public void write(int b) throws IOException {
throw new IOException("Stream closed");
}
}
/**
* Represents a source of subprocess input or a destination of
* subprocess output.
*
* Each {@code Redirect} instance is one of the following:
*
* <ul>
* <li>the special value {@link #PIPE Redirect.PIPE}
* <li>the special value {@link #INHERIT Redirect.INHERIT}
* <li>a redirection to read from a file, created by an invocation of
* {@link Redirect#from Redirect.from(File)}
* <li>a redirection to write to a file, created by an invocation of
* {@link Redirect#to Redirect.to(File)}
* <li>a redirection to append to a file, created by an invocation of
* {@link Redirect#appendTo Redirect.appendTo(File)}
* </ul>
*
* <p>Each of the above categories has an associated unique
* {@link Type Type}.
*
* @since 1.7
*/
public static abstract class Redirect {
/**
* The type of a {@link Redirect}.
*/
public enum Type {
/**
* The type of {@link Redirect#PIPE Redirect.PIPE}.
*/
PIPE,
/**
* The type of {@link Redirect#INHERIT Redirect.INHERIT}.
*/
INHERIT,
/**
* The type of redirects returned from
* {@link Redirect#from Redirect.from(File)}.
*/
READ,
/**
* The type of redirects returned from
* {@link Redirect#to Redirect.to(File)}.
*/
WRITE,
/**
* The type of redirects returned from
* {@link Redirect#appendTo Redirect.appendTo(File)}.
*/
APPEND
};
/**
* Returns the type of this {@code Redirect}.
* @return the type of this {@code Redirect}
*/
public abstract Type type();
/**
* Indicates that subprocess I/O will be connected to the
* current Java process over a pipe.
*
* This is the default handling of subprocess standard I/O.
*
* <p>It will always be true that
* <pre> {@code
* Redirect.PIPE.file() == null &&
* Redirect.PIPE.type() == Redirect.Type.PIPE
* }</pre>
*/
public static final Redirect PIPE = new Redirect() {
public Type type() { return Type.PIPE; }
public String toString() { return type().toString(); }};
/**
* Indicates that subprocess I/O source or destination will be the
* same as those of the current process. This is the normal
* behavior of most operating system command interpreters (shells).
*
* <p>It will always be true that
* <pre> {@code
* Redirect.INHERIT.file() == null &&
* Redirect.INHERIT.type() == Redirect.Type.INHERIT
* }</pre>
*/
public static final Redirect INHERIT = new Redirect() {
public Type type() { return Type.INHERIT; }
public String toString() { return type().toString(); }};
/**
* Returns the {@link File} source or destination associated
* with this redirect, or {@code null} if there is no such file.
*
* @return the file associated with this redirect,
* or {@code null} if there is no such file
*/
public File file() { return null; }
FileOutputStream toFileOutputStream() throws IOException {
throw new UnsupportedOperationException();
}
/**
* Returns a redirect to read from the specified file.
*
* <p>It will always be true that
* <pre> {@code
* Redirect.from(file).file() == file &&
* Redirect.from(file).type() == Redirect.Type.READ
* }</pre>
*
* @throws NullPointerException if the specified file is null
* @return a redirect to read from the specified file
*/
public static Redirect from(final File file) {
if (file == null)
throw new NullPointerException();
return new Redirect() {
public Type type() { return Type.READ; }
public File file() { return file; }
public String toString() {
return "redirect to read from file \"" + file + "\"";
}
};
}
/**
* Returns a redirect to write to the specified file.
* If the specified file exists when the subprocess is started,
* its previous contents will be discarded.
*
* <p>It will always be true that
* <pre> {@code
* Redirect.to(file).file() == file &&
* Redirect.to(file).type() == Redirect.Type.WRITE
* }</pre>
*
* @throws NullPointerException if the specified file is null
* @return a redirect to write to the specified file
*/
public static Redirect to(final File file) {
if (file == null)
throw new NullPointerException();
return new Redirect() {
public Type type() { return Type.WRITE; }
public File file() { return file; }
public String toString() {
return "redirect to write to file \"" + file + "\"";
}
FileOutputStream toFileOutputStream() throws IOException {
return new FileOutputStream(file, false);
}
};
}
/**
* Returns a redirect to append to the specified file.
* Each write operation first advances the position to the
* end of the file and then writes the requested data.
* Whether the advancement of the position and the writing
* of the data are done in a single atomic operation is
* system-dependent and therefore unspecified.
*
* <p>It will always be true that
* <pre> {@code
* Redirect.appendTo(file).file() == file &&
* Redirect.appendTo(file).type() == Redirect.Type.APPEND
* }</pre>
*
* @throws NullPointerException if the specified file is null
* @return a redirect to append to the specified file
*/
public static Redirect appendTo(final File file) {
if (file == null)
throw new NullPointerException();
return new Redirect() {
public Type type() { return Type.APPEND; }
public File file() { return file; }
public String toString() {
return "redirect to append to file \"" + file + "\"";
}
FileOutputStream toFileOutputStream() throws IOException {
return new FileOutputStream(file, true);
}
};
}
/**
* Compares the specified object with this {@code Redirect} for
* equality. Returns {@code true} if and only if the two
* objects are identical or both objects are {@code Redirect}
* instances of the same type associated with non-null equal
* {@code File} instances.
*/
public boolean equals(Object obj) {
if (obj == this)
return true;
if (! (obj instanceof Redirect))
return false;
Redirect r = (Redirect) obj;
if (r.type() != this.type())
return false;
assert this.file() != null;
return this.file().equals(r.file());
}
/**
* Returns a hash code value for this {@code Redirect}.
* @return a hash code value for this {@code Redirect}
*/
public int hashCode() {
File file = file();
if (file == null)
return super.hashCode();
else
return file.hashCode();
}
/**
* No public constructors. Clients must use predefined
* static {@code Redirect} instances or factory methods.
*/
private Redirect() {}
}
private Redirect[] redirects() {
if (redirects == null)
redirects = new Redirect[] {
Redirect.PIPE, Redirect.PIPE, Redirect.PIPE
};
return redirects;
}
/**
* Sets this process builder's standard input source.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method obtain their standard input from this source.
*
* <p>If the source is {@link Redirect#PIPE Redirect.PIPE}
* (the initial value), then the standard input of a
* subprocess can be written to using the output stream
* returned by {@link Process#getOutputStream()}.
* If the source is set to any other value, then
* {@link Process#getOutputStream()} will return a
* <a href="#redirect-input">null output stream</a>.
*
* @param source the new standard input source
* @return this process builder
* @throws IllegalArgumentException
* if the redirect does not correspond to a valid source
* of data, that is, has type
* {@link Redirect.Type#WRITE WRITE} or
* {@link Redirect.Type#APPEND APPEND}
* @since 1.7
*/
public ProcessBuilder redirectInput(Redirect source) {
if (source.type() == Redirect.Type.WRITE ||
source.type() == Redirect.Type.APPEND)
throw new IllegalArgumentException(
"Redirect invalid for reading: " + source);
redirects()[0] = source;
return this;
}
/**
* Sets this process builder's standard output destination.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method send their standard output to this destination.
*
* <p>If the destination is {@link Redirect#PIPE Redirect.PIPE}
* (the initial value), then the standard output of a subprocess
* can be read using the input stream returned by {@link
* Process#getInputStream()}.
* If the destination is set to any other value, then
* {@link Process#getInputStream()} will return a
* <a href="#redirect-output">null input stream</a>.
*
* @param destination the new standard output destination
* @return this process builder
* @throws IllegalArgumentException
* if the redirect does not correspond to a valid
* destination of data, that is, has type
* {@link Redirect.Type#READ READ}
* @since 1.7
*/
public ProcessBuilder redirectOutput(Redirect destination) {
if (destination.type() == Redirect.Type.READ)
throw new IllegalArgumentException(
"Redirect invalid for writing: " + destination);
redirects()[1] = destination;
return this;
}
/**
* Sets this process builder's standard error destination.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method send their standard error to this destination.
*
* <p>If the destination is {@link Redirect#PIPE Redirect.PIPE}
* (the initial value), then the error output of a subprocess
* can be read using the input stream returned by {@link
* Process#getErrorStream()}.
* If the destination is set to any other value, then
* {@link Process#getErrorStream()} will return a
* <a href="#redirect-output">null input stream</a>.
*
* <p>If the {@link #redirectErrorStream redirectErrorStream}
* attribute has been set {@code true}, then the redirection set
* by this method has no effect.
*
* @param destination the new standard error destination
* @return this process builder
* @throws IllegalArgumentException
* if the redirect does not correspond to a valid
* destination of data, that is, has type
* {@link Redirect.Type#READ READ}
* @since 1.7
*/
public ProcessBuilder redirectError(Redirect destination) {
if (destination.type() == Redirect.Type.READ)
throw new IllegalArgumentException(
"Redirect invalid for writing: " + destination);
redirects()[2] = destination;
return this;
}
/**
* Sets this process builder's standard input source to a file.
*
* <p>This is a convenience method. An invocation of the form
* {@code redirectInput(file)}
* behaves in exactly the same way as the invocation
* {@link #redirectInput(Redirect) redirectInput}
* {@code (Redirect.from(file))}.
*
* @param file the new standard input source
* @return this process builder
* @since 1.7
*/
public ProcessBuilder redirectInput(File file) {
return redirectInput(Redirect.from(file));
}
/**
* Sets this process builder's standard output destination to a file.
*
* <p>This is a convenience method. An invocation of the form
* {@code redirectOutput(file)}
* behaves in exactly the same way as the invocation
* {@link #redirectOutput(Redirect) redirectOutput}
* {@code (Redirect.to(file))}.
*
* @param file the new standard output destination
* @return this process builder
* @since 1.7
*/
public ProcessBuilder redirectOutput(File file) {
return redirectOutput(Redirect.to(file));
}
/**
* Sets this process builder's standard error destination to a file.
*
* <p>This is a convenience method. An invocation of the form
* {@code redirectError(file)}
* behaves in exactly the same way as the invocation
* {@link #redirectError(Redirect) redirectError}
* {@code (Redirect.to(file))}.
*
* @param file the new standard error destination
* @return this process builder
* @since 1.7
*/
public ProcessBuilder redirectError(File file) {
return redirectError(Redirect.to(file));
}
/**
* Returns this process builder's standard input source.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method obtain their standard input from this source.
* The initial value is {@link Redirect#PIPE Redirect.PIPE}.
*
* @return this process builder's standard input source
* @since 1.7
*/
public Redirect redirectInput() {
return (redirects == null) ? Redirect.PIPE : redirects[0];
}
/**
* Returns this process builder's standard output destination.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method redirect their standard output to this destination.
* The initial value is {@link Redirect#PIPE Redirect.PIPE}.
*
* @return this process builder's standard output destination
* @since 1.7
*/
public Redirect redirectOutput() {
return (redirects == null) ? Redirect.PIPE : redirects[1];
}
/**
* Returns this process builder's standard error destination.
*
* Subprocesses subsequently started by this object's {@link #start()}
* method redirect their standard error to this destination.
* The initial value is {@link Redirect#PIPE Redirect.PIPE}.
*
* @return this process builder's standard error destination
* @since 1.7
*/
public Redirect redirectError() {
return (redirects == null) ? Redirect.PIPE : redirects[2];
}
/**
* Sets the source and destination for subprocess standard I/O
* to be the same as those of the current Java process.
*
* <p>This is a convenience method. An invocation of the form
* <pre> {@code
* pb.inheritIO()
* }</pre>
* behaves in exactly the same way as the invocation
* <pre> {@code
* pb.redirectInput(Redirect.INHERIT)
* .redirectOutput(Redirect.INHERIT)
* .redirectError(Redirect.INHERIT)
* }</pre>
*
* This gives behavior equivalent to most operating system
* command interpreters, or the standard C library function
* {@code system()}.
*
* @return this process builder
* @since 1.7
*/
public ProcessBuilder inheritIO() {
Arrays.fill(redirects(), Redirect.INHERIT);
return this;
}
/** /**
* Tells whether this process builder merges standard error and * Tells whether this process builder merges standard error and
* standard output. * standard output.
* *
* <p>If this property is <code>true</code>, then any error output * <p>If this property is {@code true}, then any error output
* generated by subprocesses subsequently started by this object's * generated by subprocesses subsequently started by this object's
* {@link #start()} method will be merged with the standard * {@link #start()} method will be merged with the standard
* output, so that both can be read using the * output, so that both can be read using the
* {@link Process#getInputStream()} method. This makes it easier * {@link Process#getInputStream()} method. This makes it easier
* to correlate error messages with the corresponding output. * to correlate error messages with the corresponding output.
* The initial value is <code>false</code>.</p> * The initial value is {@code false}.
* *
* @return This process builder's <code>redirectErrorStream</code> property * @return this process builder's {@code redirectErrorStream} property
*/ */
public boolean redirectErrorStream() { public boolean redirectErrorStream() {
return redirectErrorStream; return redirectErrorStream;
} }
/** /**
* Sets this process builder's <code>redirectErrorStream</code> property. * Sets this process builder's {@code redirectErrorStream} property.
* *
* <p>If this property is <code>true</code>, then any error output * <p>If this property is {@code true}, then any error output
* generated by subprocesses subsequently started by this object's * generated by subprocesses subsequently started by this object's
* {@link #start()} method will be merged with the standard * {@link #start()} method will be merged with the standard
* output, so that both can be read using the * output, so that both can be read using the
* {@link Process#getInputStream()} method. This makes it easier * {@link Process#getInputStream()} method. This makes it easier
* to correlate error messages with the corresponding output. * to correlate error messages with the corresponding output.
* The initial value is <code>false</code>.</p> * The initial value is {@code false}.
* *
* @param redirectErrorStream The new property value * @param redirectErrorStream the new property value
* @return This process builder * @return this process builder
*/ */
public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) { public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) {
this.redirectErrorStream = redirectErrorStream; this.redirectErrorStream = redirectErrorStream;
@ -410,7 +937,7 @@ public final class ProcessBuilder
* <p>If there is a security manager, its * <p>If there is a security manager, its
* {@link SecurityManager#checkExec checkExec} * {@link SecurityManager#checkExec checkExec}
* method is called with the first component of this object's * method is called with the first component of this object's
* <code>command</code> array as its argument. This may result in * {@code command} array as its argument. This may result in
* a {@link SecurityException} being thrown. * a {@link SecurityException} being thrown.
* *
* <p>Starting an operating system process is highly system-dependent. * <p>Starting an operating system process is highly system-dependent.
@ -426,26 +953,42 @@ public final class ProcessBuilder
* subclass of {@link IOException}. * subclass of {@link IOException}.
* *
* <p>Subsequent modifications to this process builder will not * <p>Subsequent modifications to this process builder will not
* affect the returned {@link Process}.</p> * affect the returned {@link Process}.
* *
* @return A new {@link Process} object for managing the subprocess * @return a new {@link Process} object for managing the subprocess
* *
* @throws NullPointerException * @throws NullPointerException
* If an element of the command list is null * if an element of the command list is null
* *
* @throws IndexOutOfBoundsException * @throws IndexOutOfBoundsException
* If the command is an empty list (has size <code>0</code>) * if the command is an empty list (has size {@code 0})
* *
* @throws SecurityException * @throws SecurityException
* If a security manager exists and its * if a security manager exists and
* {@link SecurityManager#checkExec checkExec} * <ul>
* method doesn't allow creation of the subprocess
* *
* @throws IOException * <li>its
* If an I/O error occurs * {@link SecurityManager#checkExec checkExec}
* method doesn't allow creation of the subprocess, or
* *
* @see Runtime#exec(String[], String[], java.io.File) * <li>the standard input to the subprocess was
* @see SecurityManager#checkExec(String) * {@linkplain #redirectInput redirected from a file}
* and the security manager's
* {@link SecurityManager#checkRead checkRead} method
* denies read access to the file, or
*
* <li>the standard output or standard error of the
* subprocess was
* {@linkplain #redirectOutput redirected to a file}
* and the security manager's
* {@link SecurityManager#checkWrite checkWrite} method
* denies write access to the file
*
* </ul>
*
* @throws IOException if an I/O error occurs
*
* @see Runtime#exec(String[], String[], java.io.File)
*/ */
public Process start() throws IOException { public Process start() throws IOException {
// Must convert to array first -- a malicious user-supplied // Must convert to array first -- a malicious user-supplied
@ -467,6 +1010,7 @@ public final class ProcessBuilder
return ProcessImpl.start(cmdarray, return ProcessImpl.start(cmdarray,
environment, environment,
dir, dir,
redirects,
redirectErrorStream); redirectErrorStream);
} catch (IOException e) { } catch (IOException e) {
// It's much easier for us to create a high-quality error // It's much easier for us to create a high-quality error

View File

@ -33,4 +33,8 @@ import java.io.FileDescriptor;
public interface JavaIOFileDescriptorAccess { public interface JavaIOFileDescriptorAccess {
public void set(FileDescriptor obj, int fd); public void set(FileDescriptor obj, int fd);
public int get(FileDescriptor fd); public int get(FileDescriptor fd);
// Only valid on Windows
public void setHandle(FileDescriptor obj, long handle);
public long getHandle(FileDescriptor obj);
} }

View File

@ -152,11 +152,19 @@ public final class FileDescriptor {
public int get(FileDescriptor obj) { public int get(FileDescriptor obj) {
return obj.fd; return obj.fd;
} }
public void setHandle(FileDescriptor obj, long handle) {
throw new UnsupportedOperationException();
}
public long getHandle(FileDescriptor obj) {
throw new UnsupportedOperationException();
}
} }
); );
} }
// pacakge private methods used by FIS,FOS and RAF // package private methods used by FIS, FOS and RAF
int incrementAndGetUseCount() { int incrementAndGetUseCount() {
return useCount.incrementAndGet(); return useCount.incrementAndGet();

View File

@ -26,7 +26,10 @@
package java.lang; package java.lang;
import java.io.IOException; import java.io.IOException;
import java.lang.Process; import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.lang.ProcessBuilder.Redirect;
import java.lang.ProcessBuilder.Redirect;
/** /**
* This class is for the exclusive use of ProcessBuilder.start() to * This class is for the exclusive use of ProcessBuilder.start() to
@ -36,6 +39,9 @@ import java.lang.Process;
* @since 1.5 * @since 1.5
*/ */
final class ProcessImpl { final class ProcessImpl {
private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
private ProcessImpl() {} // Not instantiable private ProcessImpl() {} // Not instantiable
private static byte[] toCString(String s) { private static byte[] toCString(String s) {
@ -54,6 +60,7 @@ final class ProcessImpl {
static Process start(String[] cmdarray, static Process start(String[] cmdarray,
java.util.Map<String,String> environment, java.util.Map<String,String> environment,
String dir, String dir,
ProcessBuilder.Redirect[] redirects,
boolean redirectErrorStream) boolean redirectErrorStream)
throws IOException throws IOException
{ {
@ -78,11 +85,61 @@ final class ProcessImpl {
int[] envc = new int[1]; int[] envc = new int[1];
byte[] envBlock = ProcessEnvironment.toEnvironmentBlock(environment, envc); byte[] envBlock = ProcessEnvironment.toEnvironmentBlock(environment, envc);
int[] std_fds;
FileInputStream f0 = null;
FileOutputStream f1 = null;
FileOutputStream f2 = null;
try {
if (redirects == null) {
std_fds = new int[] { -1, -1, -1 };
} else {
std_fds = new int[3];
if (redirects[0] == Redirect.PIPE)
std_fds[0] = -1;
else if (redirects[0] == Redirect.INHERIT)
std_fds[0] = 0;
else {
f0 = new FileInputStream(redirects[0].file());
std_fds[0] = fdAccess.get(f0.getFD());
}
if (redirects[1] == Redirect.PIPE)
std_fds[1] = -1;
else if (redirects[1] == Redirect.INHERIT)
std_fds[1] = 1;
else {
f1 = redirects[1].toFileOutputStream();
std_fds[1] = fdAccess.get(f1.getFD());
}
if (redirects[2] == Redirect.PIPE)
std_fds[2] = -1;
else if (redirects[2] == Redirect.INHERIT)
std_fds[2] = 2;
else {
f2 = redirects[2].toFileOutputStream();
std_fds[2] = fdAccess.get(f2.getFD());
}
}
return new UNIXProcess return new UNIXProcess
(toCString(cmdarray[0]), (toCString(cmdarray[0]),
argBlock, args.length, argBlock, args.length,
envBlock, envc[0], envBlock, envc[0],
toCString(dir), toCString(dir),
std_fds,
redirectErrorStream); redirectErrorStream);
} finally {
// In theory, close() can throw IOException
// (although it is rather unlikely to happen here)
try { if (f0 != null) f0.close(); }
finally {
try { if (f1 != null) f1.close(); }
finally { if (f2 != null) f2.close(); }
}
}
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1995-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -34,9 +34,9 @@ import java.io.*;
*/ */
final class UNIXProcess extends Process { final class UNIXProcess extends Process {
private FileDescriptor stdin_fd; private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
private FileDescriptor stdout_fd; = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
private FileDescriptor stderr_fd;
private int pid; private int pid;
private int exitcode; private int exitcode;
private boolean hasExited; private boolean hasExited;
@ -48,15 +48,26 @@ final class UNIXProcess extends Process {
/* this is for the reaping thread */ /* this is for the reaping thread */
private native int waitForProcessExit(int pid); private native int waitForProcessExit(int pid);
/**
* Create a process using fork(2) and exec(2).
*
* @param std_fds array of file descriptors. Indexes 0, 1, and
* 2 correspond to standard input, standard output and
* standard error, respectively. On input, a value of -1
* means to create a pipe to connect child and parent
* processes. On output, a value which is not -1 is the
* parent pipe fd corresponding to the pipe which has
* been created. An element of this array is -1 on input
* if and only if it is <em>not</em> -1 on output.
* @return the pid of the subprocess
*/
private native int forkAndExec(byte[] prog, private native int forkAndExec(byte[] prog,
byte[] argBlock, int argc, byte[] argBlock, int argc,
byte[] envBlock, int envc, byte[] envBlock, int envc,
byte[] dir, byte[] dir,
boolean redirectErrorStream, int[] std_fds,
FileDescriptor stdin_fd, boolean redirectErrorStream)
FileDescriptor stdout_fd, throws IOException;
FileDescriptor stderr_fd)
throws IOException;
/* In the process constructor we wait on this gate until the process */ /* In the process constructor we wait on this gate until the process */
/* has been created. Then we return from the constructor. */ /* has been created. Then we return from the constructor. */
@ -97,67 +108,82 @@ final class UNIXProcess extends Process {
} }
UNIXProcess(final byte[] prog, UNIXProcess(final byte[] prog,
final byte[] argBlock, final int argc, final byte[] argBlock, final int argc,
final byte[] envBlock, final int envc, final byte[] envBlock, final int envc,
final byte[] dir, final byte[] dir,
final boolean redirectErrorStream) final int[] std_fds,
final boolean redirectErrorStream)
throws IOException { throws IOException {
stdin_fd = new FileDescriptor();
stdout_fd = new FileDescriptor();
stderr_fd = new FileDescriptor();
final Gate gate = new Gate(); final Gate gate = new Gate();
/* /*
* For each subprocess forked a corresponding reaper thread * For each subprocess forked a corresponding reaper thread
* is started. That thread is the only thread which waits * is started. That thread is the only thread which waits
* for the subprocess to terminate and it doesn't hold any * for the subprocess to terminate and it doesn't hold any
* locks while doing so. This design allows waitFor() and * locks while doing so. This design allows waitFor() and
* exitStatus() to be safely executed in parallel (and they * exitStatus() to be safely executed in parallel (and they
* need no native code). * need no native code).
*/ */
java.security.AccessController.doPrivileged( java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() { new java.security.PrivilegedAction<Void>() {
public Object run() { public Void run() {
Thread t = new Thread("process reaper") { Thread t = new Thread("process reaper") {
public void run() { public void run() {
try { try {
pid = forkAndExec(prog, pid = forkAndExec(prog,
argBlock, argc, argBlock, argc,
envBlock, envc, envBlock, envc,
dir, dir,
redirectErrorStream, std_fds,
stdin_fd, stdout_fd, stderr_fd); redirectErrorStream);
} catch (IOException e) { } catch (IOException e) {
gate.setException(e); /*remember to rethrow later*/ gate.setException(e); /*remember to rethrow later*/
gate.exit(); gate.exit();
return; return;
} }
java.security.AccessController.doPrivileged( java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() { new java.security.PrivilegedAction<Void>() {
public Object run() { public Void run() {
stdin_stream = new BufferedOutputStream(new if (std_fds[0] == -1)
FileOutputStream(stdin_fd)); stdin_stream = new ProcessBuilder.NullOutputStream();
stdout_stream = new BufferedInputStream(new else {
FileInputStream(stdout_fd)); FileDescriptor stdin_fd = new FileDescriptor();
stderr_stream = new FileInputStream(stderr_fd); fdAccess.set(stdin_fd, std_fds[0]);
return null; stdin_stream = new BufferedOutputStream(
new FileOutputStream(stdin_fd));
} }
});
if (std_fds[1] == -1)
stdout_stream = new ProcessBuilder.NullInputStream();
else {
FileDescriptor stdout_fd = new FileDescriptor();
fdAccess.set(stdout_fd, std_fds[1]);
stdout_stream = new BufferedInputStream(
new FileInputStream(stdout_fd));
}
if (std_fds[2] == -1)
stderr_stream = new ProcessBuilder.NullInputStream();
else {
FileDescriptor stderr_fd = new FileDescriptor();
fdAccess.set(stderr_fd, std_fds[2]);
stderr_stream = new FileInputStream(stderr_fd);
}
return null; }});
gate.exit(); /* exit from constructor */ gate.exit(); /* exit from constructor */
int res = waitForProcessExit(pid); int res = waitForProcessExit(pid);
synchronized (UNIXProcess.this) { synchronized (UNIXProcess.this) {
hasExited = true; hasExited = true;
exitcode = res; exitcode = res;
UNIXProcess.this.notifyAll(); UNIXProcess.this.notifyAll();
} }
} }
}; };
t.setDaemon(true); t.setDaemon(true);
t.start(); t.start();
return null; return null; }});
}
});
gate.waitForExit(); gate.waitForExit();
IOException e = gate.getException(); IOException e = gate.getException();
if (e != null) if (e != null)
@ -165,43 +191,43 @@ final class UNIXProcess extends Process {
} }
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
return stdin_stream; return stdin_stream;
} }
public InputStream getInputStream() { public InputStream getInputStream() {
return stdout_stream; return stdout_stream;
} }
public InputStream getErrorStream() { public InputStream getErrorStream() {
return stderr_stream; return stderr_stream;
} }
public synchronized int waitFor() throws InterruptedException { public synchronized int waitFor() throws InterruptedException {
while (!hasExited) { while (!hasExited) {
wait(); wait();
} }
return exitcode; return exitcode;
} }
public synchronized int exitValue() { public synchronized int exitValue() {
if (!hasExited) { if (!hasExited) {
throw new IllegalThreadStateException("process hasn't exited"); throw new IllegalThreadStateException("process hasn't exited");
} }
return exitcode; return exitcode;
} }
private static native void destroyProcess(int pid); private static native void destroyProcess(int pid);
public void destroy() { public void destroy() {
// There is a risk that pid will be recycled, causing us to // There is a risk that pid will be recycled, causing us to
// kill the wrong process! So we only terminate processes // kill the wrong process! So we only terminate processes
// that appear to still be running. Even with this check, // that appear to still be running. Even with this check,
// there is an unavoidable race condition here, but the window // there is an unavoidable race condition here, but the window
// is very small, and OSes try hard to not recycle pids too // is very small, and OSes try hard to not recycle pids too
// soon, so this is quite safe. // soon, so this is quite safe.
synchronized (this) { synchronized (this) {
if (!hasExited) if (!hasExited)
destroyProcess(pid); destroyProcess(pid);
} }
try { try {
stdin_stream.close(); stdin_stream.close();
stdout_stream.close(); stdout_stream.close();
@ -215,6 +241,6 @@ final class UNIXProcess extends Process {
private static native void initIDs(); private static native void initIDs();
static { static {
initIDs(); initIDs();
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1995-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -33,129 +33,155 @@ import java.io.*;
*/ */
final class UNIXProcess extends Process { final class UNIXProcess extends Process {
private FileDescriptor stdin_fd; private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
private FileDescriptor stdout_fd; = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
private FileDescriptor stderr_fd;
private int pid; private final int pid;
private int exitcode; private int exitcode;
private boolean hasExited; private boolean hasExited;
private OutputStream stdin_stream; private OutputStream stdin_stream;
private BufferedInputStream stdout_stream; private InputStream stdout_stream;
private DeferredCloseInputStream stdout_inner_stream; private DeferredCloseInputStream stdout_inner_stream;
private DeferredCloseInputStream stderr_stream; private InputStream stderr_stream;
/* this is for the reaping thread */ /* this is for the reaping thread */
private native int waitForProcessExit(int pid); private native int waitForProcessExit(int pid);
/**
* Create a process using fork(2) and exec(2).
*
* @param std_fds array of file descriptors. Indexes 0, 1, and
* 2 correspond to standard input, standard output and
* standard error, respectively. On input, a value of -1
* means to create a pipe to connect child and parent
* processes. On output, a value which is not -1 is the
* parent pipe fd corresponding to the pipe which has
* been created. An element of this array is -1 on input
* if and only if it is <em>not</em> -1 on output.
* @return the pid of the subprocess
*/
private native int forkAndExec(byte[] prog, private native int forkAndExec(byte[] prog,
byte[] argBlock, int argc, byte[] argBlock, int argc,
byte[] envBlock, int envc, byte[] envBlock, int envc,
byte[] dir, byte[] dir,
boolean redirectErrorStream, int[] std_fds,
FileDescriptor stdin_fd, boolean redirectErrorStream)
FileDescriptor stdout_fd, throws IOException;
FileDescriptor stderr_fd)
throws IOException;
UNIXProcess(final byte[] prog, UNIXProcess(final byte[] prog,
final byte[] argBlock, int argc, final byte[] argBlock, int argc,
final byte[] envBlock, int envc, final byte[] envBlock, int envc,
final byte[] dir, final byte[] dir,
final boolean redirectErrorStream) final int[] std_fds,
final boolean redirectErrorStream)
throws IOException { throws IOException {
stdin_fd = new FileDescriptor(); pid = forkAndExec(prog,
stdout_fd = new FileDescriptor(); argBlock, argc,
stderr_fd = new FileDescriptor(); envBlock, envc,
dir,
std_fds,
redirectErrorStream);
pid = forkAndExec(prog, java.security.AccessController.doPrivileged(
argBlock, argc, new java.security.PrivilegedAction<Void>() { public Void run() {
envBlock, envc, if (std_fds[0] == -1)
dir, stdin_stream = new ProcessBuilder.NullOutputStream();
redirectErrorStream, else {
stdin_fd, stdout_fd, stderr_fd); FileDescriptor stdin_fd = new FileDescriptor();
fdAccess.set(stdin_fd, std_fds[0]);
stdin_stream = new BufferedOutputStream(
new FileOutputStream(stdin_fd));
}
java.security.AccessController.doPrivileged( if (std_fds[1] == -1)
new java.security.PrivilegedAction() { stdout_stream = new ProcessBuilder.NullInputStream();
public Object run() { else {
stdin_stream FileDescriptor stdout_fd = new FileDescriptor();
= new BufferedOutputStream(new FileOutputStream(stdin_fd)); fdAccess.set(stdout_fd, std_fds[1]);
stdout_inner_stream = new DeferredCloseInputStream(stdout_fd); stdout_inner_stream = new DeferredCloseInputStream(stdout_fd);
stdout_stream = new BufferedInputStream(stdout_inner_stream); stdout_stream = new BufferedInputStream(stdout_inner_stream);
stderr_stream = new DeferredCloseInputStream(stderr_fd); }
return null;
}
});
/* if (std_fds[2] == -1)
* For each subprocess forked a corresponding reaper thread stderr_stream = new ProcessBuilder.NullInputStream();
* is started. That thread is the only thread which waits else {
* for the subprocess to terminate and it doesn't hold any FileDescriptor stderr_fd = new FileDescriptor();
* locks while doing so. This design allows waitFor() and fdAccess.set(stderr_fd, std_fds[2]);
* exitStatus() to be safely executed in parallel (and they stderr_stream = new DeferredCloseInputStream(stderr_fd);
* need no native code). }
*/
java.security.AccessController.doPrivileged( return null; }});
new java.security.PrivilegedAction() {
public Object run() { /*
Thread t = new Thread("process reaper") { * For each subprocess forked a corresponding reaper thread
public void run() { * is started. That thread is the only thread which waits
int res = waitForProcessExit(pid); * for the subprocess to terminate and it doesn't hold any
synchronized (UNIXProcess.this) { * locks while doing so. This design allows waitFor() and
hasExited = true; * exitStatus() to be safely executed in parallel (and they
exitcode = res; * need no native code).
UNIXProcess.this.notifyAll(); */
}
} java.security.AccessController.doPrivileged(
}; new java.security.PrivilegedAction<Void>() { public Void run() {
t.setDaemon(true); Thread t = new Thread("process reaper") {
t.start(); public void run() {
return null; int res = waitForProcessExit(pid);
} synchronized (UNIXProcess.this) {
}); hasExited = true;
exitcode = res;
UNIXProcess.this.notifyAll();
}
}
};
t.setDaemon(true);
t.start();
return null; }});
} }
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
return stdin_stream; return stdin_stream;
} }
public InputStream getInputStream() { public InputStream getInputStream() {
return stdout_stream; return stdout_stream;
} }
public InputStream getErrorStream() { public InputStream getErrorStream() {
return stderr_stream; return stderr_stream;
} }
public synchronized int waitFor() throws InterruptedException { public synchronized int waitFor() throws InterruptedException {
while (!hasExited) { while (!hasExited) {
wait(); wait();
} }
return exitcode; return exitcode;
} }
public synchronized int exitValue() { public synchronized int exitValue() {
if (!hasExited) { if (!hasExited) {
throw new IllegalThreadStateException("process hasn't exited"); throw new IllegalThreadStateException("process hasn't exited");
} }
return exitcode; return exitcode;
} }
private static native void destroyProcess(int pid); private static native void destroyProcess(int pid);
public synchronized void destroy() { public synchronized void destroy() {
// There is a risk that pid will be recycled, causing us to // There is a risk that pid will be recycled, causing us to
// kill the wrong process! So we only terminate processes // kill the wrong process! So we only terminate processes
// that appear to still be running. Even with this check, // that appear to still be running. Even with this check,
// there is an unavoidable race condition here, but the window // there is an unavoidable race condition here, but the window
// is very small, and OSes try hard to not recycle pids too // is very small, and OSes try hard to not recycle pids too
// soon, so this is quite safe. // soon, so this is quite safe.
if (!hasExited) if (!hasExited)
destroyProcess(pid); destroyProcess(pid);
try { try {
stdin_stream.close(); stdin_stream.close();
stdout_inner_stream.closeDeferred(stdout_stream); if (stdout_inner_stream != null)
stderr_stream.closeDeferred(stderr_stream); stdout_inner_stream.closeDeferred(stdout_stream);
if (stderr_stream instanceof DeferredCloseInputStream)
((DeferredCloseInputStream) stderr_stream)
.closeDeferred(stderr_stream);
} catch (IOException e) { } catch (IOException e) {
// ignore // ignore
} }
@ -172,99 +198,99 @@ final class UNIXProcess extends Process {
// (EOF) as they did before. // (EOF) as they did before.
// //
private static class DeferredCloseInputStream private static class DeferredCloseInputStream
extends FileInputStream extends FileInputStream
{ {
private DeferredCloseInputStream(FileDescriptor fd) { private DeferredCloseInputStream(FileDescriptor fd) {
super(fd); super(fd);
} }
private Object lock = new Object(); // For the following fields private Object lock = new Object(); // For the following fields
private boolean closePending = false; private boolean closePending = false;
private int useCount = 0; private int useCount = 0;
private InputStream streamToClose; private InputStream streamToClose;
private void raise() { private void raise() {
synchronized (lock) { synchronized (lock) {
useCount++; useCount++;
} }
} }
private void lower() throws IOException { private void lower() throws IOException {
synchronized (lock) { synchronized (lock) {
useCount--; useCount--;
if (useCount == 0 && closePending) { if (useCount == 0 && closePending) {
streamToClose.close(); streamToClose.close();
} }
} }
} }
// stc is the actual stream to be closed; it might be this object, or // stc is the actual stream to be closed; it might be this object, or
// it might be an upstream object for which this object is downstream. // it might be an upstream object for which this object is downstream.
// //
private void closeDeferred(InputStream stc) throws IOException { private void closeDeferred(InputStream stc) throws IOException {
synchronized (lock) { synchronized (lock) {
if (useCount == 0) { if (useCount == 0) {
stc.close(); stc.close();
} else { } else {
closePending = true; closePending = true;
streamToClose = stc; streamToClose = stc;
} }
} }
} }
public void close() throws IOException { public void close() throws IOException {
synchronized (lock) { synchronized (lock) {
useCount = 0; useCount = 0;
closePending = false; closePending = false;
} }
super.close(); super.close();
} }
public int read() throws IOException { public int read() throws IOException {
raise(); raise();
try { try {
return super.read(); return super.read();
} finally { } finally {
lower(); lower();
} }
} }
public int read(byte[] b) throws IOException { public int read(byte[] b) throws IOException {
raise(); raise();
try { try {
return super.read(b); return super.read(b);
} finally { } finally {
lower(); lower();
} }
} }
public int read(byte[] b, int off, int len) throws IOException { public int read(byte[] b, int off, int len) throws IOException {
raise(); raise();
try { try {
return super.read(b, off, len); return super.read(b, off, len);
} finally { } finally {
lower(); lower();
} }
} }
public long skip(long n) throws IOException { public long skip(long n) throws IOException {
raise(); raise();
try { try {
return super.skip(n); return super.skip(n);
} finally { } finally {
lower(); lower();
} }
} }
public int available() throws IOException { public int available() throws IOException {
raise(); raise();
try { try {
return super.available(); return super.available();
} finally { } finally {
lower(); lower();
} }
} }
} }
@ -272,6 +298,6 @@ final class UNIXProcess extends Process {
private static native void initIDs(); private static native void initIDs();
static { static {
initIDs(); initIDs();
} }
} }

View File

@ -1,5 +1,5 @@
/* /*
* Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved. * Copyright 1995-2008 Sun Microsystems, Inc. All Rights Reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
* *
* This code is free software; you can redistribute it and/or modify it * This code is free software; you can redistribute it and/or modify it
@ -491,10 +491,8 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
jbyteArray argBlock, jint argc, jbyteArray argBlock, jint argc,
jbyteArray envBlock, jint envc, jbyteArray envBlock, jint envc,
jbyteArray dir, jbyteArray dir,
jboolean redirectErrorStream, jintArray std_fds,
jobject stdin_fd, jboolean redirectErrorStream)
jobject stdout_fd,
jobject stderr_fd)
{ {
int errnum; int errnum;
int resultPid = -1; int resultPid = -1;
@ -505,6 +503,7 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
const char *pargBlock = getBytes(env, argBlock); const char *pargBlock = getBytes(env, argBlock);
const char *penvBlock = getBytes(env, envBlock); const char *penvBlock = getBytes(env, envBlock);
const char *pdir = getBytes(env, dir); const char *pdir = getBytes(env, dir);
jint *fds = NULL;
in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1; in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1;
@ -527,9 +526,13 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
initVectorFromBlock(envv, penvBlock, envc); initVectorFromBlock(envv, penvBlock, envc);
} }
if ((pipe(in) < 0) || assert(std_fds != NULL);
(pipe(out) < 0) || fds = (*env)->GetIntArrayElements(env, std_fds, NULL);
(pipe(err) < 0) || if (fds == NULL) goto Catch;
if ((fds[0] == -1 && pipe(in) < 0) ||
(fds[1] == -1 && pipe(out) < 0) ||
(fds[2] == -1 && pipe(err) < 0) ||
(pipe(fail) < 0)) { (pipe(fail) < 0)) {
throwIOException(env, errno, "Bad file descriptor"); throwIOException(env, errno, "Bad file descriptor");
goto Catch; goto Catch;
@ -544,23 +547,26 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
if (resultPid == 0) { if (resultPid == 0) {
/* Child process */ /* Child process */
/* Close the parent sides of the pipe. /* Close the parent sides of the pipes.
Give the child sides of the pipes the right fileno's.
Closing pipe fds here is redundant, since closeDescriptors() Closing pipe fds here is redundant, since closeDescriptors()
would do it anyways, but a little paranoia is a good thing. */ would do it anyways, but a little paranoia is a good thing. */
closeSafely(in[1]);
closeSafely(out[0]);
closeSafely(err[0]);
closeSafely(fail[0]);
/* Give the child sides of the pipes the right fileno's. */
/* Note: it is possible for in[0] == 0 */ /* Note: it is possible for in[0] == 0 */
close(in[1]); moveDescriptor(in[0] != -1 ? in[0] : fds[0], STDIN_FILENO);
moveDescriptor(in[0], STDIN_FILENO); moveDescriptor(out[1]!= -1 ? out[1] : fds[1], STDOUT_FILENO);
close(out[0]);
moveDescriptor(out[1], STDOUT_FILENO);
close(err[0]);
if (redirectErrorStream) { if (redirectErrorStream) {
close(err[1]); closeSafely(err[1]);
dup2(STDOUT_FILENO, STDERR_FILENO); dup2(STDOUT_FILENO, STDERR_FILENO);
} else { } else {
moveDescriptor(err[1], STDERR_FILENO); moveDescriptor(err[1] != -1 ? err[1] : fds[2], STDERR_FILENO);
} }
close(fail[0]);
moveDescriptor(fail[1], FAIL_FILENO); moveDescriptor(fail[1], FAIL_FILENO);
/* close everything */ /* close everything */
@ -606,9 +612,9 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
goto Catch; goto Catch;
} }
(*env)->SetIntField(env, stdin_fd, IO_fd_fdID, in [1]); fds[0] = (in [1] != -1) ? in [1] : -1;
(*env)->SetIntField(env, stdout_fd, IO_fd_fdID, out[0]); fds[1] = (out[0] != -1) ? out[0] : -1;
(*env)->SetIntField(env, stderr_fd, IO_fd_fdID, err[0]); fds[2] = (err[0] != -1) ? err[0] : -1;
Finally: Finally:
/* Always clean up the child's side of the pipes */ /* Always clean up the child's side of the pipes */
@ -628,6 +634,9 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
releaseBytes(env, envBlock, penvBlock); releaseBytes(env, envBlock, penvBlock);
releaseBytes(env, dir, pdir); releaseBytes(env, dir, pdir);
if (fds != NULL)
(*env)->ReleaseIntArrayElements(env, std_fds, fds, 0);
return resultPid; return resultPid;
Catch: Catch:

View File

@ -29,17 +29,14 @@ import java.util.concurrent.atomic.AtomicInteger;
/** /**
* Instances of the file descriptor class serve as an opaque handle * Instances of the file descriptor class serve as an opaque handle
* to the underlying machine-specific structure representing an open * to the underlying machine-specific structure representing an
* file, an open socket, or another source or sink of bytes. The * open file, an open socket, or another source or sink of bytes.
* main practical use for a file descriptor is to create a * The main practical use for a file descriptor is to create a
* <code>FileInputStream</code> or <code>FileOutputStream</code> to * {@link FileInputStream} or {@link FileOutputStream} to contain it.
* contain it. *
* <p> * <p>Applications should not create their own file descriptors.
* Applications should not create their own file descriptors.
* *
* @author Pavani Diwanji * @author Pavani Diwanji
* @see java.io.FileInputStream
* @see java.io.FileOutputStream
* @since JDK1.0 * @since JDK1.0
*/ */
public final class FileDescriptor { public final class FileDescriptor {
@ -81,6 +78,14 @@ public final class FileDescriptor {
public int get(FileDescriptor obj) { public int get(FileDescriptor obj) {
return obj.fd; return obj.fd;
} }
public void setHandle(FileDescriptor obj, long handle) {
obj.handle = handle;
}
public long getHandle(FileDescriptor obj) {
return obj.handle;
}
} }
); );
} }
@ -88,7 +93,7 @@ public final class FileDescriptor {
/** /**
* A handle to the standard input stream. Usually, this file * A handle to the standard input stream. Usually, this file
* descriptor is not used directly, but rather via the input stream * descriptor is not used directly, but rather via the input stream
* known as <code>System.in</code>. * known as {@code System.in}.
* *
* @see java.lang.System#in * @see java.lang.System#in
*/ */
@ -97,7 +102,7 @@ public final class FileDescriptor {
/** /**
* A handle to the standard output stream. Usually, this file * A handle to the standard output stream. Usually, this file
* descriptor is not used directly, but rather via the output stream * descriptor is not used directly, but rather via the output stream
* known as <code>System.out</code>. * known as {@code System.out}.
* @see java.lang.System#out * @see java.lang.System#out
*/ */
public static final FileDescriptor out = standardStream(1); public static final FileDescriptor out = standardStream(1);
@ -105,7 +110,7 @@ public final class FileDescriptor {
/** /**
* A handle to the standard error stream. Usually, this file * A handle to the standard error stream. Usually, this file
* descriptor is not used directly, but rather via the output stream * descriptor is not used directly, but rather via the output stream
* known as <code>System.err</code>. * known as {@code System.err}.
* *
* @see java.lang.System#err * @see java.lang.System#err
*/ */
@ -114,9 +119,9 @@ public final class FileDescriptor {
/** /**
* Tests if this file descriptor object is valid. * Tests if this file descriptor object is valid.
* *
* @return <code>true</code> if the file descriptor object represents a * @return {@code true} if the file descriptor object represents a
* valid, open file, socket, or other active I/O connection; * valid, open file, socket, or other active I/O connection;
* <code>false</code> otherwise. * {@code false} otherwise.
*/ */
public boolean valid() { public boolean valid() {
return ((handle != -1) || (fd != -1)); return ((handle != -1) || (fd != -1));

View File

@ -25,7 +25,16 @@
package java.lang; package java.lang;
import java.io.*; import java.io.IOException;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileDescriptor;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.lang.ProcessBuilder.Redirect;
/* This class is for the exclusive use of ProcessBuilder.start() to /* This class is for the exclusive use of ProcessBuilder.start() to
* create new processes. * create new processes.
@ -35,30 +44,82 @@ import java.io.*;
*/ */
final class ProcessImpl extends Process { final class ProcessImpl extends Process {
private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
// System-dependent portion of ProcessBuilder.start() // System-dependent portion of ProcessBuilder.start()
static Process start(String cmdarray[], static Process start(String cmdarray[],
java.util.Map<String,String> environment, java.util.Map<String,String> environment,
String dir, String dir,
ProcessBuilder.Redirect[] redirects,
boolean redirectErrorStream) boolean redirectErrorStream)
throws IOException throws IOException
{ {
String envblock = ProcessEnvironment.toEnvironmentBlock(environment); String envblock = ProcessEnvironment.toEnvironmentBlock(environment);
return new ProcessImpl(cmdarray, envblock, dir, redirectErrorStream);
FileInputStream f0 = null;
FileOutputStream f1 = null;
FileOutputStream f2 = null;
try {
long[] stdHandles;
if (redirects == null) {
stdHandles = new long[] { -1L, -1L, -1L };
} else {
stdHandles = new long[3];
if (redirects[0] == Redirect.PIPE)
stdHandles[0] = -1L;
else if (redirects[0] == Redirect.INHERIT)
stdHandles[0] = fdAccess.getHandle(FileDescriptor.in);
else {
f0 = new FileInputStream(redirects[0].file());
stdHandles[0] = fdAccess.getHandle(f0.getFD());
}
if (redirects[1] == Redirect.PIPE)
stdHandles[1] = -1L;
else if (redirects[1] == Redirect.INHERIT)
stdHandles[1] = fdAccess.getHandle(FileDescriptor.out);
else {
f1 = redirects[1].toFileOutputStream();
stdHandles[1] = fdAccess.getHandle(f1.getFD());
}
if (redirects[2] == Redirect.PIPE)
stdHandles[2] = -1L;
else if (redirects[2] == Redirect.INHERIT)
stdHandles[2] = fdAccess.getHandle(FileDescriptor.err);
else {
f2 = redirects[2].toFileOutputStream();
stdHandles[2] = fdAccess.getHandle(f2.getFD());
}
}
return new ProcessImpl(cmdarray, envblock, dir,
stdHandles, redirectErrorStream);
} finally {
// In theory, close() can throw IOException
// (although it is rather unlikely to happen here)
try { if (f0 != null) f0.close(); }
finally {
try { if (f1 != null) f1.close(); }
finally { if (f2 != null) f2.close(); }
}
}
} }
private long handle = 0; private long handle = 0;
private FileDescriptor stdin_fd;
private FileDescriptor stdout_fd;
private FileDescriptor stderr_fd;
private OutputStream stdin_stream; private OutputStream stdin_stream;
private InputStream stdout_stream; private InputStream stdout_stream;
private InputStream stderr_stream; private InputStream stderr_stream;
private ProcessImpl(String cmd[], private ProcessImpl(final String cmd[],
String envblock, final String envblock,
String path, final String path,
boolean redirectErrorStream) final long[] stdHandles,
final boolean redirectErrorStream)
throws IOException throws IOException
{ {
// Win32 CreateProcess requires cmd[0] to be normalized // Win32 CreateProcess requires cmd[0] to be normalized
@ -91,25 +152,39 @@ final class ProcessImpl extends Process {
} }
String cmdstr = cmdbuf.toString(); String cmdstr = cmdbuf.toString();
stdin_fd = new FileDescriptor(); handle = create(cmdstr, envblock, path,
stdout_fd = new FileDescriptor(); stdHandles, redirectErrorStream);
stderr_fd = new FileDescriptor();
handle = create(cmdstr, envblock, path, redirectErrorStream,
stdin_fd, stdout_fd, stderr_fd);
java.security.AccessController.doPrivileged( java.security.AccessController.doPrivileged(
new java.security.PrivilegedAction() { new java.security.PrivilegedAction<Void>() {
public Object run() { public Void run() {
stdin_stream = if (stdHandles[0] == -1L)
new BufferedOutputStream(new FileOutputStream(stdin_fd)); stdin_stream = new ProcessBuilder.NullOutputStream();
stdout_stream = else {
new BufferedInputStream(new FileInputStream(stdout_fd)); FileDescriptor stdin_fd = new FileDescriptor();
stderr_stream = fdAccess.setHandle(stdin_fd, stdHandles[0]);
new FileInputStream(stderr_fd); stdin_stream = new BufferedOutputStream(
return null; new FileOutputStream(stdin_fd));
} }
});
if (stdHandles[1] == -1L)
stdout_stream = new ProcessBuilder.NullInputStream();
else {
FileDescriptor stdout_fd = new FileDescriptor();
fdAccess.setHandle(stdout_fd, stdHandles[1]);
stdout_stream = new BufferedInputStream(
new FileInputStream(stdout_fd));
}
if (stdHandles[2] == -1L)
stderr_stream = new ProcessBuilder.NullInputStream();
else {
FileDescriptor stderr_fd = new FileDescriptor();
fdAccess.setHandle(stderr_fd, stdHandles[2]);
stderr_stream = new FileInputStream(stderr_fd);
}
return null; }});
} }
public OutputStream getOutputStream() { public OutputStream getOutputStream() {
@ -150,13 +225,30 @@ final class ProcessImpl extends Process {
public void destroy() { terminateProcess(handle); } public void destroy() { terminateProcess(handle); }
private static native void terminateProcess(long handle); private static native void terminateProcess(long handle);
/**
* Create a process using the win32 function CreateProcess.
*
* @param cmdstr the Windows commandline
* @param envblock NUL-separated, double-NUL-terminated list of
* environment strings in VAR=VALUE form
* @param dir the working directory of the process, or null if
* inheriting the current directory from the parent process
* @param stdHandles array of windows HANDLEs. Indexes 0, 1, and
* 2 correspond to standard input, standard output and
* standard error, respectively. On input, a value of -1
* means to create a pipe to connect child and parent
* processes. On output, a value which is not -1 is the
* parent pipe handle corresponding to the pipe which has
* been created. An element of this array is -1 on input
* if and only if it is <em>not</em> -1 on output.
* @param redirectErrorStream redirectErrorStream attribute
* @return the native subprocess HANDLE returned by CreateProcess
*/
private static native long create(String cmdstr, private static native long create(String cmdstr,
String envblock, String envblock,
String dir, String dir,
boolean redirectErrorStream, long[] stdHandles,
FileDescriptor in_fd, boolean redirectErrorStream)
FileDescriptor out_fd,
FileDescriptor err_fd)
throws IOException; throws IOException;
private static native boolean closeHandle(long handle); private static native boolean closeHandle(long handle);

View File

@ -125,7 +125,7 @@ win32Error(JNIEnv *env, const char *functionName)
static void static void
closeSafely(HANDLE handle) closeSafely(HANDLE handle)
{ {
if (handle) if (handle != INVALID_HANDLE_VALUE)
CloseHandle(handle); CloseHandle(handle);
} }
@ -134,23 +134,22 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
jstring cmd, jstring cmd,
jstring envBlock, jstring envBlock,
jstring dir, jstring dir,
jboolean redirectErrorStream, jlongArray stdHandles,
jobject in_fd, jboolean redirectErrorStream)
jobject out_fd,
jobject err_fd)
{ {
HANDLE inRead = 0; HANDLE inRead = INVALID_HANDLE_VALUE;
HANDLE inWrite = 0; HANDLE inWrite = INVALID_HANDLE_VALUE;
HANDLE outRead = 0; HANDLE outRead = INVALID_HANDLE_VALUE;
HANDLE outWrite = 0; HANDLE outWrite = INVALID_HANDLE_VALUE;
HANDLE errRead = 0; HANDLE errRead = INVALID_HANDLE_VALUE;
HANDLE errWrite = 0; HANDLE errWrite = INVALID_HANDLE_VALUE;
SECURITY_ATTRIBUTES sa; SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pi; PROCESS_INFORMATION pi;
STARTUPINFO si; STARTUPINFO si;
LPTSTR pcmd = NULL; LPTSTR pcmd = NULL;
LPCTSTR pdir = NULL; LPCTSTR pdir = NULL;
LPVOID penvBlock = NULL; LPVOID penvBlock = NULL;
jlong *handles = NULL;
jlong ret = 0; jlong ret = 0;
OSVERSIONINFO ver; OSVERSIONINFO ver;
jboolean onNT = JNI_FALSE; jboolean onNT = JNI_FALSE;
@ -161,17 +160,6 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT) if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT)
onNT = JNI_TRUE; onNT = JNI_TRUE;
sa.nLength = sizeof(sa);
sa.lpSecurityDescriptor = 0;
sa.bInheritHandle = TRUE;
if (!(CreatePipe(&inRead, &inWrite, &sa, PIPE_SIZE) &&
CreatePipe(&outRead, &outWrite, &sa, PIPE_SIZE) &&
CreatePipe(&errRead, &errWrite, &sa, PIPE_SIZE))) {
win32Error(env, "CreatePipe");
goto Catch;
}
assert(cmd != NULL); assert(cmd != NULL);
pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL); pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL);
if (pcmd == NULL) goto Catch; if (pcmd == NULL) goto Catch;
@ -189,19 +177,62 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
if (penvBlock == NULL) goto Catch; if (penvBlock == NULL) goto Catch;
} }
assert(stdHandles != NULL);
handles = (*env)->GetLongArrayElements(env, stdHandles, NULL);
if (handles == NULL) goto Catch;
memset(&si, 0, sizeof(si)); memset(&si, 0, sizeof(si));
si.cb = sizeof(si); si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES; si.dwFlags = STARTF_USESTDHANDLES;
si.hStdInput = inRead;
si.hStdOutput = outWrite;
si.hStdError = redirectErrorStream ? outWrite : errWrite;
SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE); sa.nLength = sizeof(sa);
SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE); sa.lpSecurityDescriptor = 0;
SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE); sa.bInheritHandle = TRUE;
if (redirectErrorStream) if (handles[0] != (jlong) -1) {
SetHandleInformation(errWrite, HANDLE_FLAG_INHERIT, FALSE); si.hStdInput = (HANDLE) handles[0];
handles[0] = (jlong) -1;
} else {
if (! CreatePipe(&inRead, &inWrite, &sa, PIPE_SIZE)) {
win32Error(env, "CreatePipe");
goto Catch;
}
si.hStdInput = inRead;
SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE);
handles[0] = (jlong) inWrite;
}
SetHandleInformation(si.hStdInput, HANDLE_FLAG_INHERIT, TRUE);
if (handles[1] != (jlong) -1) {
si.hStdOutput = (HANDLE) handles[1];
handles[1] = (jlong) -1;
} else {
if (! CreatePipe(&outRead, &outWrite, &sa, PIPE_SIZE)) {
win32Error(env, "CreatePipe");
goto Catch;
}
si.hStdOutput = outWrite;
SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE);
handles[1] = (jlong) outRead;
}
SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT, TRUE);
if (redirectErrorStream) {
si.hStdError = si.hStdOutput;
handles[2] = (jlong) -1;
} else if (handles[2] != (jlong) -1) {
si.hStdError = (HANDLE) handles[2];
handles[2] = (jlong) -1;
} else {
if (! CreatePipe(&errRead, &errWrite, &sa, PIPE_SIZE)) {
win32Error(env, "CreatePipe");
goto Catch;
}
si.hStdError = errWrite;
SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE);
handles[2] = (jlong) errRead;
}
SetHandleInformation(si.hStdError, HANDLE_FLAG_INHERIT, TRUE);
if (onNT) if (onNT)
processFlag = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT; processFlag = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
@ -237,9 +268,6 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
CloseHandle(pi.hThread); CloseHandle(pi.hThread);
ret = (jlong)pi.hProcess; ret = (jlong)pi.hProcess;
(*env)->SetLongField(env, in_fd, IO_handle_fdID, (jlong)inWrite);
(*env)->SetLongField(env, out_fd, IO_handle_fdID, (jlong)outRead);
(*env)->SetLongField(env, err_fd, IO_handle_fdID, (jlong)errRead);
Finally: Finally:
/* Always clean up the child's side of the pipes */ /* Always clean up the child's side of the pipes */
@ -257,6 +285,9 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
else else
JNU_ReleaseStringPlatformChars(env, dir, (char *) penvBlock); JNU_ReleaseStringPlatformChars(env, dir, (char *) penvBlock);
} }
if (handles != NULL)
(*env)->ReleaseLongArrayElements(env, stdHandles, handles, 0);
return ret; return ret;
Catch: Catch:

View File

@ -25,12 +25,15 @@
* @test * @test
* @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689 * @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689
* 5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313 * 5026830 5023243 5070673 4052517 4811767 6192449 6397034 6413313
* 6464154 6523983 6206031 * 6464154 6523983 6206031 4960438 6631352 6631966
* @summary Basic tests for Process and Environment Variable code * @summary Basic tests for Process and Environment Variable code
* @run main/othervm Basic * @run main/othervm Basic
* @author Martin Buchholz * @author Martin Buchholz
*/ */
import java.lang.ProcessBuilder.Redirect;
import static java.lang.ProcessBuilder.Redirect.*;
import java.io.*; import java.io.*;
import java.util.*; import java.util.*;
import java.security.*; import java.security.*;
@ -257,7 +260,29 @@ public class Basic {
public static class JavaChild { public static class JavaChild {
public static void main(String args[]) throws Throwable { public static void main(String args[]) throws Throwable {
String action = args[0]; String action = args[0];
if (action.equals("System.getenv(String)")) { if (action.equals("testIO")) {
String expected = "standard input";
char[] buf = new char[expected.length()+1];
int n = new InputStreamReader(System.in).read(buf,0,buf.length);
if (n != expected.length())
System.exit(5);
if (! new String(buf,0,n).equals(expected))
System.exit(5);
System.err.print("standard error");
System.out.print("standard output");
} else if (action.equals("testInheritIO")) {
List<String> childArgs = new ArrayList<String>(javaChildArgs);
childArgs.add("testIO");
ProcessBuilder pb = new ProcessBuilder(childArgs);
pb.inheritIO();
ProcessResults r = run(pb);
if (! r.out().equals(""))
System.exit(7);
if (! r.err().equals(""))
System.exit(8);
if (r.exitValue() != 0)
System.exit(9);
} else if (action.equals("System.getenv(String)")) {
String val = System.getenv(args[1]); String val = System.getenv(args[1]);
printUTF8(val == null ? "null" : val); printUTF8(val == null ? "null" : val);
} else if (action.equals("System.getenv(\\u1234)")) { } else if (action.equals("System.getenv(\\u1234)")) {
@ -599,6 +624,333 @@ public class Basic {
} catch (Throwable t) { unexpected(t); } } catch (Throwable t) { unexpected(t); }
} }
static void checkRedirects(ProcessBuilder pb,
Redirect in, Redirect out, Redirect err) {
equal(pb.redirectInput(), in);
equal(pb.redirectOutput(), out);
equal(pb.redirectError(), err);
}
static void redirectIO(ProcessBuilder pb,
Redirect in, Redirect out, Redirect err) {
pb.redirectInput(in);
pb.redirectOutput(out);
pb.redirectError(err);
}
static void setFileContents(File file, String contents) {
try {
Writer w = new FileWriter(file);
w.write(contents);
w.close();
} catch (Throwable t) { unexpected(t); }
}
static String fileContents(File file) {
try {
Reader r = new FileReader(file);
StringBuilder sb = new StringBuilder();
char[] buffer = new char[1024];
int n;
while ((n = r.read(buffer)) != -1)
sb.append(buffer,0,n);
r.close();
return new String(sb);
} catch (Throwable t) { unexpected(t); return ""; }
}
static void testIORedirection() throws Throwable {
final File ifile = new File("ifile");
final File ofile = new File("ofile");
final File efile = new File("efile");
ifile.delete();
ofile.delete();
efile.delete();
//----------------------------------------------------------------
// Check mutual inequality of different types of Redirect
//----------------------------------------------------------------
Redirect[] redirects =
{ PIPE,
INHERIT,
Redirect.from(ifile),
Redirect.to(ifile),
Redirect.appendTo(ifile),
Redirect.from(ofile),
Redirect.to(ofile),
Redirect.appendTo(ofile),
};
for (int i = 0; i < redirects.length; i++)
for (int j = 0; j < redirects.length; j++)
equal(redirects[i].equals(redirects[j]), (i == j));
//----------------------------------------------------------------
// Check basic properties of different types of Redirect
//----------------------------------------------------------------
equal(PIPE.type(), Redirect.Type.PIPE);
equal(PIPE.toString(), "PIPE");
equal(PIPE.file(), null);
equal(INHERIT.type(), Redirect.Type.INHERIT);
equal(INHERIT.toString(), "INHERIT");
equal(INHERIT.file(), null);
equal(Redirect.from(ifile).type(), Redirect.Type.READ);
equal(Redirect.from(ifile).toString(),
"redirect to read from file \"ifile\"");
equal(Redirect.from(ifile).file(), ifile);
equal(Redirect.from(ifile),
Redirect.from(ifile));
equal(Redirect.from(ifile).hashCode(),
Redirect.from(ifile).hashCode());
equal(Redirect.to(ofile).type(), Redirect.Type.WRITE);
equal(Redirect.to(ofile).toString(),
"redirect to write to file \"ofile\"");
equal(Redirect.to(ofile).file(), ofile);
equal(Redirect.to(ofile),
Redirect.to(ofile));
equal(Redirect.to(ofile).hashCode(),
Redirect.to(ofile).hashCode());
equal(Redirect.appendTo(ofile).type(), Redirect.Type.APPEND);
equal(Redirect.appendTo(efile).toString(),
"redirect to append to file \"efile\"");
equal(Redirect.appendTo(efile).file(), efile);
equal(Redirect.appendTo(efile),
Redirect.appendTo(efile));
equal(Redirect.appendTo(efile).hashCode(),
Redirect.appendTo(efile).hashCode());
//----------------------------------------------------------------
// Check initial values of redirects
//----------------------------------------------------------------
List<String> childArgs = new ArrayList<String>(javaChildArgs);
childArgs.add("testIO");
final ProcessBuilder pb = new ProcessBuilder(childArgs);
checkRedirects(pb, PIPE, PIPE, PIPE);
//----------------------------------------------------------------
// Check inheritIO
//----------------------------------------------------------------
pb.inheritIO();
checkRedirects(pb, INHERIT, INHERIT, INHERIT);
//----------------------------------------------------------------
// Check setters and getters agree
//----------------------------------------------------------------
pb.redirectInput(ifile);
equal(pb.redirectInput().file(), ifile);
equal(pb.redirectInput(), Redirect.from(ifile));
pb.redirectOutput(ofile);
equal(pb.redirectOutput().file(), ofile);
equal(pb.redirectOutput(), Redirect.to(ofile));
pb.redirectError(efile);
equal(pb.redirectError().file(), efile);
equal(pb.redirectError(), Redirect.to(efile));
THROWS(IllegalArgumentException.class,
new Fun(){void f() {
pb.redirectInput(Redirect.to(ofile)); }},
new Fun(){void f() {
pb.redirectInput(Redirect.appendTo(ofile)); }},
new Fun(){void f() {
pb.redirectOutput(Redirect.from(ifile)); }},
new Fun(){void f() {
pb.redirectError(Redirect.from(ifile)); }});
THROWS(IOException.class,
// Input file does not exist
new Fun(){void f() throws Throwable { pb.start(); }});
setFileContents(ifile, "standard input");
//----------------------------------------------------------------
// Writing to non-existent files
//----------------------------------------------------------------
{
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile), "standard output");
equal(fileContents(efile), "standard error");
equal(r.out(), "");
equal(r.err(), "");
ofile.delete();
efile.delete();
}
//----------------------------------------------------------------
// Both redirectErrorStream + redirectError
//----------------------------------------------------------------
{
pb.redirectErrorStream(true);
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile),
"standard error" + "standard output");
equal(fileContents(efile), "");
equal(r.out(), "");
equal(r.err(), "");
ofile.delete();
efile.delete();
}
//----------------------------------------------------------------
// Appending to existing files
//----------------------------------------------------------------
{
setFileContents(ofile, "ofile-contents");
setFileContents(efile, "efile-contents");
pb.redirectOutput(Redirect.appendTo(ofile));
pb.redirectError(Redirect.appendTo(efile));
pb.redirectErrorStream(false);
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile),
"ofile-contents" + "standard output");
equal(fileContents(efile),
"efile-contents" + "standard error");
equal(r.out(), "");
equal(r.err(), "");
ofile.delete();
efile.delete();
}
//----------------------------------------------------------------
// Replacing existing files
//----------------------------------------------------------------
{
setFileContents(ofile, "ofile-contents");
setFileContents(efile, "efile-contents");
pb.redirectOutput(ofile);
pb.redirectError(Redirect.to(efile));
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile), "standard output");
equal(fileContents(efile), "standard error");
equal(r.out(), "");
equal(r.err(), "");
ofile.delete();
efile.delete();
}
//----------------------------------------------------------------
// Appending twice to the same file?
//----------------------------------------------------------------
{
setFileContents(ofile, "ofile-contents");
setFileContents(efile, "efile-contents");
Redirect appender = Redirect.appendTo(ofile);
pb.redirectOutput(appender);
pb.redirectError(appender);
ProcessResults r = run(pb);
equal(r.exitValue(), 0);
equal(fileContents(ofile),
"ofile-contents" +
"standard error" +
"standard output");
equal(fileContents(efile), "efile-contents");
equal(r.out(), "");
equal(r.err(), "");
ifile.delete();
ofile.delete();
efile.delete();
}
//----------------------------------------------------------------
// Testing INHERIT is harder.
// Note that this requires __FOUR__ nested JVMs involved in one test,
// if you count the harness JVM.
//----------------------------------------------------------------
{
redirectIO(pb, PIPE, PIPE, PIPE);
List<String> command = pb.command();
command.set(command.size() - 1, "testInheritIO");
Process p = pb.start();
new PrintStream(p.getOutputStream()).print("standard input");
p.getOutputStream().close();
ProcessResults r = run(p);
equal(r.exitValue(), 0);
equal(r.out(), "standard output");
equal(r.err(), "standard error");
}
//----------------------------------------------------------------
// Test security implications of I/O redirection
//----------------------------------------------------------------
// Read access to current directory is always granted;
// So create a tmpfile for input instead.
final File tmpFile = File.createTempFile("Basic", "tmp");
setFileContents(tmpFile, "standard input");
final Policy policy = new Policy();
Policy.setPolicy(policy);
System.setSecurityManager(new SecurityManager());
try {
final Permission xPermission
= new FilePermission("<<ALL FILES>>", "execute");
final Permission rxPermission
= new FilePermission("<<ALL FILES>>", "read,execute");
final Permission wxPermission
= new FilePermission("<<ALL FILES>>", "write,execute");
final Permission rwxPermission
= new FilePermission("<<ALL FILES>>", "read,write,execute");
THROWS(SecurityException.class,
new Fun() { void f() throws IOException {
policy.setPermissions(xPermission);
redirectIO(pb, from(tmpFile), PIPE, PIPE);
pb.start();}},
new Fun() { void f() throws IOException {
policy.setPermissions(rxPermission);
redirectIO(pb, PIPE, to(ofile), PIPE);
pb.start();}},
new Fun() { void f() throws IOException {
policy.setPermissions(rxPermission);
redirectIO(pb, PIPE, PIPE, to(efile));
pb.start();}});
{
policy.setPermissions(rxPermission);
redirectIO(pb, from(tmpFile), PIPE, PIPE);
ProcessResults r = run(pb);
equal(r.out(), "standard output");
equal(r.err(), "standard error");
}
{
policy.setPermissions(wxPermission);
redirectIO(pb, PIPE, to(ofile), to(efile));
Process p = pb.start();
new PrintStream(p.getOutputStream()).print("standard input");
p.getOutputStream().close();
ProcessResults r = run(p);
policy.setPermissions(rwxPermission);
equal(fileContents(ofile), "standard output");
equal(fileContents(efile), "standard error");
}
{
policy.setPermissions(rwxPermission);
redirectIO(pb, from(tmpFile), to(ofile), to(efile));
ProcessResults r = run(pb);
policy.setPermissions(rwxPermission);
equal(fileContents(ofile), "standard output");
equal(fileContents(efile), "standard error");
}
} finally {
policy.setPermissions(new RuntimePermission("setSecurityManager"));
System.setSecurityManager(null);
tmpFile.delete();
ifile.delete();
ofile.delete();
efile.delete();
}
}
private static void realMain(String[] args) throws Throwable { private static void realMain(String[] args) throws Throwable {
if (Windows.is()) if (Windows.is())
System.out.println("This appears to be a Windows system."); System.out.println("This appears to be a Windows system.");
@ -607,6 +959,9 @@ public class Basic {
if (UnicodeOS.is()) if (UnicodeOS.is())
System.out.println("This appears to be a Unicode-based OS."); System.out.println("This appears to be a Unicode-based OS.");
try { testIORedirection(); }
catch (Throwable t) { unexpected(t); }
//---------------------------------------------------------------- //----------------------------------------------------------------
// Basic tests for setting, replacing and deleting envvars // Basic tests for setting, replacing and deleting envvars
//---------------------------------------------------------------- //----------------------------------------------------------------
@ -1354,7 +1709,8 @@ public class Basic {
execPermission); execPermission);
ProcessBuilder pb = new ProcessBuilder("env"); ProcessBuilder pb = new ProcessBuilder("env");
pb.environment().put("foo","bar"); pb.environment().put("foo","bar");
pb.start(); Process p = pb.start();
closeStreams(p);
} catch (IOException e) { // OK } catch (IOException e) { // OK
} catch (Throwable t) { unexpected(t); } } catch (Throwable t) { unexpected(t); }
@ -1378,6 +1734,14 @@ public class Basic {
} }
static void closeStreams(Process p) {
try {
p.getOutputStream().close();
p.getInputStream().close();
p.getErrorStream().close();
} catch (Throwable t) { unexpected(t); }
}
//---------------------------------------------------------------- //----------------------------------------------------------------
// A Policy class designed to make permissions fiddling very easy. // A Policy class designed to make permissions fiddling very easy.
//---------------------------------------------------------------- //----------------------------------------------------------------
@ -1432,10 +1796,19 @@ public class Basic {
} }
} catch (Throwable t) { } catch (Throwable t) {
throwable = t; throwable = t;
} finally {
try { is.close(); }
catch (Throwable t) { throwable = t; }
} }
} }
} }
static ProcessResults run(ProcessBuilder pb) {
try {
return run(pb.start());
} catch (Throwable t) { unexpected(t); return null; }
}
private static ProcessResults run(Process p) { private static ProcessResults run(Process p) {
Throwable throwable = null; Throwable throwable = null;
int exitValue = -1; int exitValue = -1;