4960438: (process) Need IO redirection API for subprocesses
Reviewed-by: alanb, iris
--- a/jdk/src/share/classes/java/lang/Process.java Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/share/classes/java/lang/Process.java Mon Mar 10 14:32:51 2008 -0700
@@ -41,18 +41,24 @@
* <p>The methods that create processes may not work well for special
* processes on certain native platforms, such as native windowing
* processes, daemon processes, Win16/DOS processes on Microsoft
- * Windows, or shell scripts. The created subprocess does not have
- * its own terminal or console. All its standard I/O (i.e. stdin,
- * stdout, stderr) operations will be redirected to the parent process
- * through three streams
- * ({@link #getOutputStream()},
- * {@link #getInputStream()},
- * {@link #getErrorStream()}).
+ * Windows, or shell scripts.
+ *
+ * <p>By default, the created subprocess does not have its own terminal
+ * or console. All its standard I/O (i.e. stdin, stdout, stderr)
+ * operations will be redirected to the parent process, where they can
+ * be accessed via the streams obtained using the methods
+ * {@link #getOutputStream()},
+ * {@link #getInputStream()}, and
+ * {@link #getErrorStream()}.
* The parent process uses these streams to feed input to and get output
* from the subprocess. Because some native platforms only provide
* limited buffer size for standard input and output streams, failure
* to promptly write the input stream or read the output stream of
- * the 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
* the {@code Process} object, but rather the subprocess
@@ -62,16 +68,22 @@
* Process} object execute asynchronously or concurrently with respect
* to the Java process that owns the {@code Process} object.
*
- * @author unascribed
- * @see ProcessBuilder
+ * <p>As of 1.5, {@link ProcessBuilder#start()} is the preferred way
+ * to create a {@code Process}.
+ *
* @since JDK1.0
*/
public abstract class Process {
/**
* Returns the output stream connected to the normal input of the
* subprocess. Output to the stream is piped into the standard
- * input stream of the process represented by this {@code Process}
- * object.
+ * input of the process represented by this {@code Process} 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
* output stream to be buffered.
@@ -84,30 +96,47 @@
/**
* Returns the input stream connected to the normal output of the
* subprocess. The stream obtains data piped from the standard
- * output stream of the process represented by this {@code
- * Process} object.
+ * output of the process represented by this {@code 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
* input stream to be buffered.
*
* @return the input stream connected to the normal output of the
* subprocess
- * @see ProcessBuilder#redirectErrorStream()
*/
abstract public InputStream getInputStream();
/**
- * Returns the input stream connected to the error output stream of
- * the subprocess. The stream obtains data piped from the error
- * output stream of the process represented by this {@code Process}
- * object.
+ * Returns the input stream connected to the error output of the
+ * subprocess. The stream obtains data piped from the error output
+ * of the process represented by this {@code Process} 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
* 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
- * @see ProcessBuilder#redirectErrorStream()
*/
abstract public InputStream getErrorStream();
--- a/jdk/src/share/classes/java/lang/ProcessBuilder.java Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/share/classes/java/lang/ProcessBuilder.java Mon Mar 10 14:32:51 2008 -0700
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -27,6 +27,10 @@
import java.io.File;
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.List;
import java.util.Map;
@@ -34,7 +38,7 @@
/**
* 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
* {@link Process} instance with those attributes. The {@link
* #start()} method can be invoked repeatedly from the same instance
@@ -59,19 +63,64 @@
*
* <li>a <i>working directory</i>. The default value is the current
* 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
- * 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
* be accessed using the {@link Process#getInputStream()} and {@link
- * Process#getErrorStream()} methods. If the value is set to
- * <code>true</code>, the standard error is merged with the standard
- * output. This makes it easier to correlate error messages with the
- * corresponding output. In this case, the merged data can be read
- * from the stream returned by {@link Process#getInputStream()}, while
- * reading from the stream returned by {@link
- * Process#getErrorStream()} will get an immediate end of file.
+ * Process#getErrorStream()} methods.
+ *
+ * <p>If the value is set to {@code true}, then:
+ *
+ * <ul>
+ * <li>standard error is merged with the standard output and always sent
+ * 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>
*
@@ -87,34 +136,43 @@
* is invoked.
*
* <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
* attributes structurally, it <i>must</i> be synchronized externally.
*
* <p>Starting a new process which uses the default working directory
* and environment is easy:
*
- * <blockquote><pre>
+ * <pre> {@code
* Process p = new ProcessBuilder("myCommand", "myArg").start();
- * </pre></blockquote>
+ * }</pre>
*
* <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>
- * ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2");
- * Map<String, String> env = pb.environment();
+ * <pre> {@code
+ * ProcessBuilder pb =
+ * new ProcessBuilder("myCommand", "myArg1", "myArg2");
+ * Map<String, String> env = pb.environment();
* env.put("VAR1", "myValue");
* env.remove("OTHERVAR");
* env.put("VAR2", env.get("VAR1") + "suffix");
* pb.directory(new File("myDir"));
+ * File log = new File("log");
+ * pb.redirectErrorStream(true);
+ * pb.redirectOutput(Redirect.appendTo(log));
* 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
* variables, first call {@link java.util.Map#clear() Map.clear()}
* before adding environment variables.
*
+ * @author Martin Buchholz
* @since 1.5
*/
@@ -124,20 +182,19 @@
private File directory;
private Map<String,String> environment;
private boolean redirectErrorStream;
+ private Redirect[] redirects;
/**
* Constructs a process builder with the specified operating
* 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
* process builder. It is not checked whether
- * <code>command</code> corresponds to a valid operating system
- * command.</p>
+ * {@code command} corresponds to a valid operating system
+ * command.
*
- * @param command The list containing the program and its arguments
- *
- * @throws NullPointerException
- * If the argument is <code>null</code>
+ * @param command the list containing the program and its arguments
+ * @throws NullPointerException if the argument is null
*/
public ProcessBuilder(List<String> command) {
if (command == null)
@@ -149,12 +206,12 @@
* Constructs a process builder with the specified operating
* system program and arguments. This is a convenience
* 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
- * <code>command</code> corresponds to a valid operating system
- * command.</p>
+ * {@code command} corresponds to a valid 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
*/
public ProcessBuilder(String... command) {
this.command = new ArrayList<String>(command.length);
@@ -165,16 +222,15 @@
/**
* Sets this process builder's operating system program and
* 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
- * checked whether <code>command</code> corresponds to a valid
- * operating system command.</p>
+ * checked whether {@code command} corresponds to a valid
+ * operating system command.
*
- * @param command The list containing the program and its arguments
- * @return This process builder
+ * @param command the list containing the program and its arguments
+ * @return this process builder
*
- * @throws NullPointerException
- * If the argument is <code>null</code>
+ * @throws NullPointerException if the argument is null
*/
public ProcessBuilder command(List<String> command) {
if (command == null)
@@ -187,12 +243,12 @@
* Sets this process builder's operating system program and
* arguments. This is a convenience method that sets the command
* to a string list containing the same strings as the
- * <code>command</code> array, in the same order. It is not
- * checked whether <code>command</code> corresponds to a valid
- * operating system command.</p>
+ * {@code command} array, in the same order. It is not
+ * checked whether {@code command} corresponds to a valid
+ * operating system command.
*
- * @param command A string array containing the program and its arguments
- * @return This process builder
+ * @param command a string array containing the program and its arguments
+ * @return this process builder
*/
public ProcessBuilder command(String... command) {
this.command = new ArrayList<String>(command.length);
@@ -205,9 +261,9 @@
* Returns this process builder's operating system program and
* arguments. The returned list is <i>not</i> a copy. Subsequent
* 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() {
return command;
@@ -225,10 +281,10 @@
* <p>The returned object may be modified using ordinary {@link
* java.util.Map Map} operations. These modifications will be
* 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
* 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}.
*
* <p>If the system does not support environment variables, an
@@ -262,25 +318,24 @@
* <p>The returned map is typically case-sensitive on all platforms.
*
* <p>If a security manager exists, its
- * {@link SecurityManager#checkPermission checkPermission}
- * method is called with a
- * <code>{@link RuntimePermission}("getenv.*")</code>
- * permission. This may result in a {@link SecurityException} being
- * thrown.
+ * {@link SecurityManager#checkPermission checkPermission} method
+ * is called with a
+ * {@link RuntimePermission}{@code ("getenv.*")} permission.
+ * This may result in a {@link SecurityException} being thrown.
*
* <p>When passing information to a Java subprocess,
* <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
- * If a security manager exists and its
- * {@link SecurityManager#checkPermission checkPermission}
- * method doesn't allow access to the process environment
+ * @throws SecurityException
+ * if a security manager exists and its
+ * {@link SecurityManager#checkPermission checkPermission}
+ * method doesn't allow access to the process environment
*
- * @see Runtime#exec(String[],String[],java.io.File)
- * @see System#getenv()
+ * @see Runtime#exec(String[],String[],java.io.File)
+ * @see System#getenv()
*/
public Map<String,String> environment() {
SecurityManager security = System.getSecurityManager();
@@ -328,12 +383,12 @@
*
* Subprocesses subsequently started by this object's {@link
* #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
- * directory named by the system property <code>user.dir</code>,
- * as the working directory of the child process.</p>
+ * directory named by the system property {@code user.dir},
+ * 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() {
return directory;
@@ -344,50 +399,522 @@
*
* Subprocesses subsequently started by this object's {@link
* #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
- * directory named by the system property <code>user.dir</code>,
- * as the working directory of the child process.</p>
+ * directory named by the system property {@code user.dir},
+ * as the working directory of the child process.
*
- * @param directory The new working directory
- * @return This process builder
+ * @param directory the new working directory
+ * @return this process builder
*/
public ProcessBuilder directory(File directory) {
this.directory = directory;
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
* 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
* {@link #start()} method will be merged with the standard
* output, so that both can be read using the
* {@link Process#getInputStream()} method. This makes it easier
* 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() {
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
* {@link #start()} method will be merged with the standard
* output, so that both can be read using the
* {@link Process#getInputStream()} method. This makes it easier
* 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
- * @return This process builder
+ * @param redirectErrorStream the new property value
+ * @return this process builder
*/
public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) {
this.redirectErrorStream = redirectErrorStream;
@@ -410,7 +937,7 @@
* <p>If there is a security manager, its
* {@link SecurityManager#checkExec checkExec}
* 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.
*
* <p>Starting an operating system process is highly system-dependent.
@@ -426,26 +953,42 @@
* subclass of {@link IOException}.
*
* <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
+ * if an element of the command list is null
*
- * @throws NullPointerException
- * If an element of the command list is null
+ * @throws IndexOutOfBoundsException
+ * if the command is an empty list (has size {@code 0})
*
- * @throws IndexOutOfBoundsException
- * If the command is an empty list (has size <code>0</code>)
+ * @throws SecurityException
+ * if a security manager exists and
+ * <ul>
+ *
+ * <li>its
+ * {@link SecurityManager#checkExec checkExec}
+ * method doesn't allow creation of the subprocess, or
*
- * @throws SecurityException
- * If a security manager exists and its
- * {@link SecurityManager#checkExec checkExec}
- * method doesn't allow creation of the subprocess
+ * <li>the standard input to the subprocess was
+ * {@linkplain #redirectInput redirected from a file}
+ * and the security manager's
+ * {@link SecurityManager#checkRead checkRead} method
+ * denies read access to the file, or
*
- * @throws IOException
- * If an I/O error occurs
+ * <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
*
- * @see Runtime#exec(String[], String[], java.io.File)
- * @see SecurityManager#checkExec(String)
+ * </ul>
+ *
+ * @throws IOException if an I/O error occurs
+ *
+ * @see Runtime#exec(String[], String[], java.io.File)
*/
public Process start() throws IOException {
// Must convert to array first -- a malicious user-supplied
@@ -467,6 +1010,7 @@
return ProcessImpl.start(cmdarray,
environment,
dir,
+ redirects,
redirectErrorStream);
} catch (IOException e) {
// It's much easier for us to create a high-quality error
--- a/jdk/src/share/classes/sun/misc/JavaIOFileDescriptorAccess.java Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/share/classes/sun/misc/JavaIOFileDescriptorAccess.java Mon Mar 10 14:32:51 2008 -0700
@@ -33,4 +33,8 @@
public interface JavaIOFileDescriptorAccess {
public void set(FileDescriptor obj, int fd);
public int get(FileDescriptor fd);
+
+ // Only valid on Windows
+ public void setHandle(FileDescriptor obj, long handle);
+ public long getHandle(FileDescriptor obj);
}
--- a/jdk/src/solaris/classes/java/io/FileDescriptor.java Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/solaris/classes/java/io/FileDescriptor.java Mon Mar 10 14:32:51 2008 -0700
@@ -152,11 +152,19 @@
public int get(FileDescriptor obj) {
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() {
return useCount.incrementAndGet();
--- a/jdk/src/solaris/classes/java/lang/ProcessImpl.java Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/solaris/classes/java/lang/ProcessImpl.java Mon Mar 10 14:32:51 2008 -0700
@@ -26,7 +26,10 @@
package java.lang;
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
@@ -36,6 +39,9 @@
* @since 1.5
*/
final class ProcessImpl {
+ private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
+ = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
+
private ProcessImpl() {} // Not instantiable
private static byte[] toCString(String s) {
@@ -54,6 +60,7 @@
static Process start(String[] cmdarray,
java.util.Map<String,String> environment,
String dir,
+ ProcessBuilder.Redirect[] redirects,
boolean redirectErrorStream)
throws IOException
{
@@ -78,11 +85,61 @@
int[] envc = new int[1];
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
(toCString(cmdarray[0]),
argBlock, args.length,
envBlock, envc[0],
toCString(dir),
+ std_fds,
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(); }
+ }
+ }
}
}
--- a/jdk/src/solaris/classes/java/lang/UNIXProcess.java.linux Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/solaris/classes/java/lang/UNIXProcess.java.linux Mon Mar 10 14:32:51 2008 -0700
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -34,9 +34,9 @@
*/
final class UNIXProcess extends Process {
- private FileDescriptor stdin_fd;
- private FileDescriptor stdout_fd;
- private FileDescriptor stderr_fd;
+ private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
+ = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
+
private int pid;
private int exitcode;
private boolean hasExited;
@@ -48,15 +48,26 @@
/* this is for the reaping thread */
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,
- byte[] argBlock, int argc,
- byte[] envBlock, int envc,
- byte[] dir,
- boolean redirectErrorStream,
- FileDescriptor stdin_fd,
- FileDescriptor stdout_fd,
- FileDescriptor stderr_fd)
- throws IOException;
+ byte[] argBlock, int argc,
+ byte[] envBlock, int envc,
+ byte[] dir,
+ int[] std_fds,
+ boolean redirectErrorStream)
+ throws IOException;
/* In the process constructor we wait on this gate until the process */
/* has been created. Then we return from the constructor. */
@@ -97,67 +108,82 @@
}
UNIXProcess(final byte[] prog,
- final byte[] argBlock, final int argc,
- final byte[] envBlock, final int envc,
- final byte[] dir,
- final boolean redirectErrorStream)
+ final byte[] argBlock, final int argc,
+ final byte[] envBlock, final int envc,
+ final byte[] dir,
+ final int[] std_fds,
+ final boolean redirectErrorStream)
throws IOException {
- stdin_fd = new FileDescriptor();
- stdout_fd = new FileDescriptor();
- stderr_fd = new FileDescriptor();
final Gate gate = new Gate();
- /*
- * For each subprocess forked a corresponding reaper thread
- * is started. That thread is the only thread which waits
- * for the subprocess to terminate and it doesn't hold any
- * locks while doing so. This design allows waitFor() and
- * exitStatus() to be safely executed in parallel (and they
- * need no native code).
- */
+ /*
+ * For each subprocess forked a corresponding reaper thread
+ * is started. That thread is the only thread which waits
+ * for the subprocess to terminate and it doesn't hold any
+ * locks while doing so. This design allows waitFor() and
+ * exitStatus() to be safely executed in parallel (and they
+ * need no native code).
+ */
- java.security.AccessController.doPrivileged(
- new java.security.PrivilegedAction() {
- public Object run() {
- Thread t = new Thread("process reaper") {
- public void run() {
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction<Void>() {
+ public Void run() {
+ Thread t = new Thread("process reaper") {
+ public void run() {
try {
pid = forkAndExec(prog,
- argBlock, argc,
- envBlock, envc,
- dir,
- redirectErrorStream,
- stdin_fd, stdout_fd, stderr_fd);
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ std_fds,
+ redirectErrorStream);
} catch (IOException e) {
gate.setException(e); /*remember to rethrow later*/
gate.exit();
return;
}
java.security.AccessController.doPrivileged(
- new java.security.PrivilegedAction() {
- public Object run() {
- stdin_stream = new BufferedOutputStream(new
- FileOutputStream(stdin_fd));
- stdout_stream = new BufferedInputStream(new
- FileInputStream(stdout_fd));
- stderr_stream = new FileInputStream(stderr_fd);
- return null;
+ new java.security.PrivilegedAction<Void>() {
+ public Void run() {
+ if (std_fds[0] == -1)
+ stdin_stream = new ProcessBuilder.NullOutputStream();
+ else {
+ FileDescriptor stdin_fd = new FileDescriptor();
+ fdAccess.set(stdin_fd, std_fds[0]);
+ 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 */
- int res = waitForProcessExit(pid);
- synchronized (UNIXProcess.this) {
- hasExited = true;
- exitcode = res;
- UNIXProcess.this.notifyAll();
- }
- }
- };
+ int res = waitForProcessExit(pid);
+ synchronized (UNIXProcess.this) {
+ hasExited = true;
+ exitcode = res;
+ UNIXProcess.this.notifyAll();
+ }
+ }
+ };
t.setDaemon(true);
t.start();
- return null;
- }
- });
+ return null; }});
gate.waitForExit();
IOException e = gate.getException();
if (e != null)
@@ -165,43 +191,43 @@
}
public OutputStream getOutputStream() {
- return stdin_stream;
+ return stdin_stream;
}
public InputStream getInputStream() {
- return stdout_stream;
+ return stdout_stream;
}
public InputStream getErrorStream() {
- return stderr_stream;
+ return stderr_stream;
}
public synchronized int waitFor() throws InterruptedException {
while (!hasExited) {
- wait();
- }
- return exitcode;
+ wait();
+ }
+ return exitcode;
}
public synchronized int exitValue() {
- if (!hasExited) {
- throw new IllegalThreadStateException("process hasn't exited");
- }
- return exitcode;
+ if (!hasExited) {
+ throw new IllegalThreadStateException("process hasn't exited");
+ }
+ return exitcode;
}
private static native void destroyProcess(int pid);
public void destroy() {
- // There is a risk that pid will be recycled, causing us to
- // kill the wrong process! So we only terminate processes
- // that appear to still be running. Even with this check,
- // there is an unavoidable race condition here, but the window
- // is very small, and OSes try hard to not recycle pids too
- // soon, so this is quite safe.
- synchronized (this) {
- if (!hasExited)
- destroyProcess(pid);
- }
+ // There is a risk that pid will be recycled, causing us to
+ // kill the wrong process! So we only terminate processes
+ // that appear to still be running. Even with this check,
+ // there is an unavoidable race condition here, but the window
+ // is very small, and OSes try hard to not recycle pids too
+ // soon, so this is quite safe.
+ synchronized (this) {
+ if (!hasExited)
+ destroyProcess(pid);
+ }
try {
stdin_stream.close();
stdout_stream.close();
@@ -215,6 +241,6 @@
private static native void initIDs();
static {
- initIDs();
+ initIDs();
}
}
--- a/jdk/src/solaris/classes/java/lang/UNIXProcess.java.solaris Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/solaris/classes/java/lang/UNIXProcess.java.solaris Mon Mar 10 14:32:51 2008 -0700
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -33,129 +33,155 @@
*/
final class UNIXProcess extends Process {
- private FileDescriptor stdin_fd;
- private FileDescriptor stdout_fd;
- private FileDescriptor stderr_fd;
- private int pid;
+ private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
+ = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
+
+ private final int pid;
private int exitcode;
private boolean hasExited;
private OutputStream stdin_stream;
- private BufferedInputStream stdout_stream;
+ private InputStream stdout_stream;
private DeferredCloseInputStream stdout_inner_stream;
- private DeferredCloseInputStream stderr_stream;
+ private InputStream stderr_stream;
/* this is for the reaping thread */
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,
- byte[] argBlock, int argc,
- byte[] envBlock, int envc,
- byte[] dir,
- boolean redirectErrorStream,
- FileDescriptor stdin_fd,
- FileDescriptor stdout_fd,
- FileDescriptor stderr_fd)
- throws IOException;
+ byte[] argBlock, int argc,
+ byte[] envBlock, int envc,
+ byte[] dir,
+ int[] std_fds,
+ boolean redirectErrorStream)
+ throws IOException;
UNIXProcess(final byte[] prog,
- final byte[] argBlock, int argc,
- final byte[] envBlock, int envc,
- final byte[] dir,
- final boolean redirectErrorStream)
+ final byte[] argBlock, int argc,
+ final byte[] envBlock, int envc,
+ final byte[] dir,
+ final int[] std_fds,
+ final boolean redirectErrorStream)
throws IOException {
- stdin_fd = new FileDescriptor();
- stdout_fd = new FileDescriptor();
- stderr_fd = new FileDescriptor();
+ pid = forkAndExec(prog,
+ argBlock, argc,
+ envBlock, envc,
+ dir,
+ std_fds,
+ redirectErrorStream);
- pid = forkAndExec(prog,
- argBlock, argc,
- envBlock, envc,
- dir,
- redirectErrorStream,
- stdin_fd, stdout_fd, stderr_fd);
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction<Void>() { public Void run() {
+ if (std_fds[0] == -1)
+ stdin_stream = new ProcessBuilder.NullOutputStream();
+ else {
+ FileDescriptor stdin_fd = new FileDescriptor();
+ fdAccess.set(stdin_fd, std_fds[0]);
+ stdin_stream = new BufferedOutputStream(
+ new FileOutputStream(stdin_fd));
+ }
- java.security.AccessController.doPrivileged(
- new java.security.PrivilegedAction() {
- public Object run() {
- stdin_stream
- = new BufferedOutputStream(new FileOutputStream(stdin_fd));
- stdout_inner_stream = new DeferredCloseInputStream(stdout_fd);
- stdout_stream = new BufferedInputStream(stdout_inner_stream);
- stderr_stream = new DeferredCloseInputStream(stderr_fd);
- return null;
- }
- });
+ if (std_fds[1] == -1)
+ stdout_stream = new ProcessBuilder.NullInputStream();
+ else {
+ FileDescriptor stdout_fd = new FileDescriptor();
+ fdAccess.set(stdout_fd, std_fds[1]);
+ stdout_inner_stream = new DeferredCloseInputStream(stdout_fd);
+ stdout_stream = new BufferedInputStream(stdout_inner_stream);
+ }
- /*
- * For each subprocess forked a corresponding reaper thread
- * is started. That thread is the only thread which waits
- * for the subprocess to terminate and it doesn't hold any
- * locks while doing so. This design allows waitFor() and
- * exitStatus() to be safely executed in parallel (and they
- * need no native code).
- */
+ 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 DeferredCloseInputStream(stderr_fd);
+ }
+
+ return null; }});
- java.security.AccessController.doPrivileged(
- new java.security.PrivilegedAction() {
- public Object run() {
- Thread t = new Thread("process reaper") {
- public void run() {
- int res = waitForProcessExit(pid);
- synchronized (UNIXProcess.this) {
- hasExited = true;
- exitcode = res;
- UNIXProcess.this.notifyAll();
- }
- }
- };
- t.setDaemon(true);
- t.start();
- return null;
- }
- });
+ /*
+ * For each subprocess forked a corresponding reaper thread
+ * is started. That thread is the only thread which waits
+ * for the subprocess to terminate and it doesn't hold any
+ * locks while doing so. This design allows waitFor() and
+ * exitStatus() to be safely executed in parallel (and they
+ * need no native code).
+ */
+
+ java.security.AccessController.doPrivileged(
+ new java.security.PrivilegedAction<Void>() { public Void run() {
+ Thread t = new Thread("process reaper") {
+ public void run() {
+ int res = waitForProcessExit(pid);
+ synchronized (UNIXProcess.this) {
+ hasExited = true;
+ exitcode = res;
+ UNIXProcess.this.notifyAll();
+ }
+ }
+ };
+ t.setDaemon(true);
+ t.start();
+ return null; }});
}
public OutputStream getOutputStream() {
- return stdin_stream;
+ return stdin_stream;
}
public InputStream getInputStream() {
- return stdout_stream;
+ return stdout_stream;
}
public InputStream getErrorStream() {
- return stderr_stream;
+ return stderr_stream;
}
public synchronized int waitFor() throws InterruptedException {
while (!hasExited) {
- wait();
- }
- return exitcode;
+ wait();
+ }
+ return exitcode;
}
public synchronized int exitValue() {
- if (!hasExited) {
- throw new IllegalThreadStateException("process hasn't exited");
- }
- return exitcode;
+ if (!hasExited) {
+ throw new IllegalThreadStateException("process hasn't exited");
+ }
+ return exitcode;
}
private static native void destroyProcess(int pid);
public synchronized void destroy() {
- // There is a risk that pid will be recycled, causing us to
- // kill the wrong process! So we only terminate processes
- // that appear to still be running. Even with this check,
- // there is an unavoidable race condition here, but the window
- // is very small, and OSes try hard to not recycle pids too
- // soon, so this is quite safe.
- if (!hasExited)
- destroyProcess(pid);
- try {
+ // There is a risk that pid will be recycled, causing us to
+ // kill the wrong process! So we only terminate processes
+ // that appear to still be running. Even with this check,
+ // there is an unavoidable race condition here, but the window
+ // is very small, and OSes try hard to not recycle pids too
+ // soon, so this is quite safe.
+ if (!hasExited)
+ destroyProcess(pid);
+ try {
stdin_stream.close();
- stdout_inner_stream.closeDeferred(stdout_stream);
- stderr_stream.closeDeferred(stderr_stream);
+ if (stdout_inner_stream != null)
+ stdout_inner_stream.closeDeferred(stdout_stream);
+ if (stderr_stream instanceof DeferredCloseInputStream)
+ ((DeferredCloseInputStream) stderr_stream)
+ .closeDeferred(stderr_stream);
} catch (IOException e) {
// ignore
}
@@ -172,99 +198,99 @@
// (EOF) as they did before.
//
private static class DeferredCloseInputStream
- extends FileInputStream
+ extends FileInputStream
{
- private DeferredCloseInputStream(FileDescriptor fd) {
- super(fd);
- }
+ private DeferredCloseInputStream(FileDescriptor fd) {
+ super(fd);
+ }
- private Object lock = new Object(); // For the following fields
- private boolean closePending = false;
- private int useCount = 0;
- private InputStream streamToClose;
+ private Object lock = new Object(); // For the following fields
+ private boolean closePending = false;
+ private int useCount = 0;
+ private InputStream streamToClose;
- private void raise() {
- synchronized (lock) {
- useCount++;
- }
- }
+ private void raise() {
+ synchronized (lock) {
+ useCount++;
+ }
+ }
- private void lower() throws IOException {
- synchronized (lock) {
- useCount--;
- if (useCount == 0 && closePending) {
- streamToClose.close();
- }
- }
- }
+ private void lower() throws IOException {
+ synchronized (lock) {
+ useCount--;
+ if (useCount == 0 && closePending) {
+ streamToClose.close();
+ }
+ }
+ }
- // 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.
- //
- private void closeDeferred(InputStream stc) throws IOException {
- synchronized (lock) {
- if (useCount == 0) {
- stc.close();
- } else {
- closePending = true;
- streamToClose = stc;
- }
- }
- }
+ // 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.
+ //
+ private void closeDeferred(InputStream stc) throws IOException {
+ synchronized (lock) {
+ if (useCount == 0) {
+ stc.close();
+ } else {
+ closePending = true;
+ streamToClose = stc;
+ }
+ }
+ }
- public void close() throws IOException {
- synchronized (lock) {
- useCount = 0;
- closePending = false;
- }
- super.close();
- }
+ public void close() throws IOException {
+ synchronized (lock) {
+ useCount = 0;
+ closePending = false;
+ }
+ super.close();
+ }
- public int read() throws IOException {
- raise();
- try {
- return super.read();
- } finally {
- lower();
- }
- }
+ public int read() throws IOException {
+ raise();
+ try {
+ return super.read();
+ } finally {
+ lower();
+ }
+ }
- public int read(byte[] b) throws IOException {
- raise();
- try {
- return super.read(b);
- } finally {
- lower();
- }
- }
+ public int read(byte[] b) throws IOException {
+ raise();
+ try {
+ return super.read(b);
+ } finally {
+ lower();
+ }
+ }
- public int read(byte[] b, int off, int len) throws IOException {
- raise();
- try {
- return super.read(b, off, len);
- } finally {
- lower();
- }
- }
+ public int read(byte[] b, int off, int len) throws IOException {
+ raise();
+ try {
+ return super.read(b, off, len);
+ } finally {
+ lower();
+ }
+ }
- public long skip(long n) throws IOException {
- raise();
- try {
- return super.skip(n);
- } finally {
- lower();
- }
- }
+ public long skip(long n) throws IOException {
+ raise();
+ try {
+ return super.skip(n);
+ } finally {
+ lower();
+ }
+ }
- public int available() throws IOException {
- raise();
- try {
- return super.available();
- } finally {
- lower();
- }
- }
+ public int available() throws IOException {
+ raise();
+ try {
+ return super.available();
+ } finally {
+ lower();
+ }
+ }
}
@@ -272,6 +298,6 @@
private static native void initIDs();
static {
- initIDs();
+ initIDs();
}
}
--- a/jdk/src/solaris/native/java/lang/UNIXProcess_md.c Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/solaris/native/java/lang/UNIXProcess_md.c Mon Mar 10 14:32:51 2008 -0700
@@ -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.
*
* This code is free software; you can redistribute it and/or modify it
@@ -491,10 +491,8 @@
jbyteArray argBlock, jint argc,
jbyteArray envBlock, jint envc,
jbyteArray dir,
- jboolean redirectErrorStream,
- jobject stdin_fd,
- jobject stdout_fd,
- jobject stderr_fd)
+ jintArray std_fds,
+ jboolean redirectErrorStream)
{
int errnum;
int resultPid = -1;
@@ -505,6 +503,7 @@
const char *pargBlock = getBytes(env, argBlock);
const char *penvBlock = getBytes(env, envBlock);
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;
@@ -527,9 +526,13 @@
initVectorFromBlock(envv, penvBlock, envc);
}
- if ((pipe(in) < 0) ||
- (pipe(out) < 0) ||
- (pipe(err) < 0) ||
+ assert(std_fds != NULL);
+ fds = (*env)->GetIntArrayElements(env, std_fds, NULL);
+ 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)) {
throwIOException(env, errno, "Bad file descriptor");
goto Catch;
@@ -544,23 +547,26 @@
if (resultPid == 0) {
/* Child process */
- /* Close the parent sides of the pipe.
- Give the child sides of the pipes the right fileno's.
+ /* Close the parent sides of the pipes.
Closing pipe fds here is redundant, since closeDescriptors()
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 */
- close(in[1]);
- moveDescriptor(in[0], STDIN_FILENO);
- close(out[0]);
- moveDescriptor(out[1], STDOUT_FILENO);
- close(err[0]);
+ moveDescriptor(in[0] != -1 ? in[0] : fds[0], STDIN_FILENO);
+ moveDescriptor(out[1]!= -1 ? out[1] : fds[1], STDOUT_FILENO);
+
if (redirectErrorStream) {
- close(err[1]);
+ closeSafely(err[1]);
dup2(STDOUT_FILENO, STDERR_FILENO);
} else {
- moveDescriptor(err[1], STDERR_FILENO);
+ moveDescriptor(err[1] != -1 ? err[1] : fds[2], STDERR_FILENO);
}
- close(fail[0]);
+
moveDescriptor(fail[1], FAIL_FILENO);
/* close everything */
@@ -606,9 +612,9 @@
goto Catch;
}
- (*env)->SetIntField(env, stdin_fd, IO_fd_fdID, in [1]);
- (*env)->SetIntField(env, stdout_fd, IO_fd_fdID, out[0]);
- (*env)->SetIntField(env, stderr_fd, IO_fd_fdID, err[0]);
+ fds[0] = (in [1] != -1) ? in [1] : -1;
+ fds[1] = (out[0] != -1) ? out[0] : -1;
+ fds[2] = (err[0] != -1) ? err[0] : -1;
Finally:
/* Always clean up the child's side of the pipes */
@@ -628,6 +634,9 @@
releaseBytes(env, envBlock, penvBlock);
releaseBytes(env, dir, pdir);
+ if (fds != NULL)
+ (*env)->ReleaseIntArrayElements(env, std_fds, fds, 0);
+
return resultPid;
Catch:
--- a/jdk/src/windows/classes/java/io/FileDescriptor.java Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/windows/classes/java/io/FileDescriptor.java Mon Mar 10 14:32:51 2008 -0700
@@ -29,17 +29,14 @@
/**
* Instances of the file descriptor class serve as an opaque handle
- * to the underlying machine-specific structure representing an open
- * file, an open socket, or another source or sink of bytes. The
- * main practical use for a file descriptor is to create a
- * <code>FileInputStream</code> or <code>FileOutputStream</code> to
- * contain it.
- * <p>
- * Applications should not create their own file descriptors.
+ * to the underlying machine-specific structure representing an
+ * open file, an open socket, or another source or sink of bytes.
+ * The main practical use for a file descriptor is to create a
+ * {@link FileInputStream} or {@link FileOutputStream} to contain it.
+ *
+ * <p>Applications should not create their own file descriptors.
*
* @author Pavani Diwanji
- * @see java.io.FileInputStream
- * @see java.io.FileOutputStream
* @since JDK1.0
*/
public final class FileDescriptor {
@@ -81,6 +78,14 @@
public int get(FileDescriptor obj) {
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 @@
/**
* A handle to the standard input stream. Usually, this file
* 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
*/
@@ -97,7 +102,7 @@
/**
* A handle to the standard output stream. Usually, this file
* 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
*/
public static final FileDescriptor out = standardStream(1);
@@ -105,7 +110,7 @@
/**
* A handle to the standard error stream. Usually, this file
* 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
*/
@@ -114,9 +119,9 @@
/**
* 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;
- * <code>false</code> otherwise.
+ * {@code false} otherwise.
*/
public boolean valid() {
return ((handle != -1) || (fd != -1));
--- a/jdk/src/windows/classes/java/lang/ProcessImpl.java Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/windows/classes/java/lang/ProcessImpl.java Mon Mar 10 14:32:51 2008 -0700
@@ -25,7 +25,16 @@
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
* create new processes.
@@ -35,30 +44,82 @@
*/
final class ProcessImpl extends Process {
+ private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
+ = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
// System-dependent portion of ProcessBuilder.start()
static Process start(String cmdarray[],
java.util.Map<String,String> environment,
String dir,
+ ProcessBuilder.Redirect[] redirects,
boolean redirectErrorStream)
throws IOException
{
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 FileDescriptor stdin_fd;
- private FileDescriptor stdout_fd;
- private FileDescriptor stderr_fd;
private OutputStream stdin_stream;
private InputStream stdout_stream;
private InputStream stderr_stream;
- private ProcessImpl(String cmd[],
- String envblock,
- String path,
- boolean redirectErrorStream)
+ private ProcessImpl(final String cmd[],
+ final String envblock,
+ final String path,
+ final long[] stdHandles,
+ final boolean redirectErrorStream)
throws IOException
{
// Win32 CreateProcess requires cmd[0] to be normalized
@@ -91,25 +152,39 @@
}
String cmdstr = cmdbuf.toString();
- stdin_fd = new FileDescriptor();
- stdout_fd = new FileDescriptor();
- stderr_fd = new FileDescriptor();
-
- handle = create(cmdstr, envblock, path, redirectErrorStream,
- stdin_fd, stdout_fd, stderr_fd);
+ handle = create(cmdstr, envblock, path,
+ stdHandles, redirectErrorStream);
java.security.AccessController.doPrivileged(
- new java.security.PrivilegedAction() {
- public Object run() {
- stdin_stream =
- new BufferedOutputStream(new FileOutputStream(stdin_fd));
- stdout_stream =
- new BufferedInputStream(new FileInputStream(stdout_fd));
- stderr_stream =
- new FileInputStream(stderr_fd);
- return null;
+ new java.security.PrivilegedAction<Void>() {
+ public Void run() {
+ if (stdHandles[0] == -1L)
+ stdin_stream = new ProcessBuilder.NullOutputStream();
+ else {
+ FileDescriptor stdin_fd = new FileDescriptor();
+ fdAccess.setHandle(stdin_fd, stdHandles[0]);
+ stdin_stream = new BufferedOutputStream(
+ 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() {
@@ -150,13 +225,30 @@
public void destroy() { terminateProcess(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,
String envblock,
String dir,
- boolean redirectErrorStream,
- FileDescriptor in_fd,
- FileDescriptor out_fd,
- FileDescriptor err_fd)
+ long[] stdHandles,
+ boolean redirectErrorStream)
throws IOException;
private static native boolean closeHandle(long handle);
--- a/jdk/src/windows/native/java/lang/ProcessImpl_md.c Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/src/windows/native/java/lang/ProcessImpl_md.c Mon Mar 10 14:32:51 2008 -0700
@@ -125,7 +125,7 @@
static void
closeSafely(HANDLE handle)
{
- if (handle)
+ if (handle != INVALID_HANDLE_VALUE)
CloseHandle(handle);
}
@@ -134,23 +134,22 @@
jstring cmd,
jstring envBlock,
jstring dir,
- jboolean redirectErrorStream,
- jobject in_fd,
- jobject out_fd,
- jobject err_fd)
+ jlongArray stdHandles,
+ jboolean redirectErrorStream)
{
- HANDLE inRead = 0;
- HANDLE inWrite = 0;
- HANDLE outRead = 0;
- HANDLE outWrite = 0;
- HANDLE errRead = 0;
- HANDLE errWrite = 0;
+ HANDLE inRead = INVALID_HANDLE_VALUE;
+ HANDLE inWrite = INVALID_HANDLE_VALUE;
+ HANDLE outRead = INVALID_HANDLE_VALUE;
+ HANDLE outWrite = INVALID_HANDLE_VALUE;
+ HANDLE errRead = INVALID_HANDLE_VALUE;
+ HANDLE errWrite = INVALID_HANDLE_VALUE;
SECURITY_ATTRIBUTES sa;
PROCESS_INFORMATION pi;
STARTUPINFO si;
LPTSTR pcmd = NULL;
LPCTSTR pdir = NULL;
LPVOID penvBlock = NULL;
+ jlong *handles = NULL;
jlong ret = 0;
OSVERSIONINFO ver;
jboolean onNT = JNI_FALSE;
@@ -161,17 +160,6 @@
if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT)
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);
pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL);
if (pcmd == NULL) goto Catch;
@@ -189,19 +177,62 @@
if (penvBlock == NULL) goto Catch;
}
+ assert(stdHandles != NULL);
+ handles = (*env)->GetLongArrayElements(env, stdHandles, NULL);
+ if (handles == NULL) goto Catch;
+
memset(&si, 0, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES;
- si.hStdInput = inRead;
- si.hStdOutput = outWrite;
- si.hStdError = redirectErrorStream ? outWrite : errWrite;
+
+ sa.nLength = sizeof(sa);
+ sa.lpSecurityDescriptor = 0;
+ sa.bInheritHandle = TRUE;
+
+ if (handles[0] != (jlong) -1) {
+ 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);
- SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE);
- SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE);
- SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE);
+ 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)
- SetHandleInformation(errWrite, HANDLE_FLAG_INHERIT, FALSE);
+ 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)
processFlag = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
@@ -237,9 +268,6 @@
CloseHandle(pi.hThread);
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:
/* Always clean up the child's side of the pipes */
@@ -257,6 +285,9 @@
else
JNU_ReleaseStringPlatformChars(env, dir, (char *) penvBlock);
}
+ if (handles != NULL)
+ (*env)->ReleaseLongArrayElements(env, stdHandles, handles, 0);
+
return ret;
Catch:
--- a/jdk/test/java/lang/ProcessBuilder/Basic.java Mon Mar 10 14:32:51 2008 -0700
+++ b/jdk/test/java/lang/ProcessBuilder/Basic.java Mon Mar 10 14:32:51 2008 -0700
@@ -25,12 +25,15 @@
* @test
* @bug 4199068 4738465 4937983 4930681 4926230 4931433 4932663 4986689
* 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
* @run main/othervm Basic
* @author Martin Buchholz
*/
+import java.lang.ProcessBuilder.Redirect;
+import static java.lang.ProcessBuilder.Redirect.*;
+
import java.io.*;
import java.util.*;
import java.security.*;
@@ -257,7 +260,29 @@
public static class JavaChild {
public static void main(String args[]) throws Throwable {
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]);
printUTF8(val == null ? "null" : val);
} else if (action.equals("System.getenv(\\u1234)")) {
@@ -599,6 +624,333 @@
} 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 {
if (Windows.is())
System.out.println("This appears to be a Windows system.");
@@ -607,6 +959,9 @@
if (UnicodeOS.is())
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
//----------------------------------------------------------------
@@ -1354,7 +1709,8 @@
execPermission);
ProcessBuilder pb = new ProcessBuilder("env");
pb.environment().put("foo","bar");
- pb.start();
+ Process p = pb.start();
+ closeStreams(p);
} catch (IOException e) { // OK
} catch (Throwable t) { unexpected(t); }
@@ -1378,6 +1734,14 @@
}
+ 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.
//----------------------------------------------------------------
@@ -1432,10 +1796,19 @@
}
} catch (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) {
Throwable throwable = null;
int exitValue = -1;