changeset 25:b5a587dd5af3

4960438: (process) Need IO redirection API for subprocesses Reviewed-by: alanb, iris
author martin
date Mon, 10 Mar 2008 14:32:51 -0700
parents 73003d04c21f
children a3ae216ca35d
files src/share/classes/java/lang/Process.java src/share/classes/java/lang/ProcessBuilder.java src/share/classes/sun/misc/JavaIOFileDescriptorAccess.java src/solaris/classes/java/io/FileDescriptor.java src/solaris/classes/java/lang/ProcessImpl.java src/solaris/classes/java/lang/UNIXProcess.java.linux src/solaris/classes/java/lang/UNIXProcess.java.solaris src/solaris/native/java/lang/UNIXProcess_md.c src/windows/classes/java/io/FileDescriptor.java src/windows/classes/java/lang/ProcessImpl.java src/windows/native/java/lang/ProcessImpl_md.c test/java/lang/ProcessBuilder/Basic.java
diffstat 12 files changed, 1665 insertions(+), 461 deletions(-) [+]
line wrap: on
line diff
--- a/src/share/classes/java/lang/Process.java	Mon Mar 10 14:32:51 2008 -0700
+++ b/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/src/share/classes/java/lang/ProcessBuilder.java	Mon Mar 10 14:32:51 2008 -0700
+++ b/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&lt;String, String&gt; 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</code>)
+     * @throws IndexOutOfBoundsException
+     *         if the command is an empty list (has size {@code 0})
      *
-     * @throws  SecurityException
-     *          If a security manager exists and its
-     *          {@link SecurityManager#checkExec checkExec}
-     *          method doesn't allow creation of the subprocess
+     * @throws SecurityException
+     *         if a security manager exists and
+     *         <ul>
      *
-     * @throws  IOException
-     *          If an I/O error occurs
+     *         <li>its
+     *         {@link SecurityManager#checkExec checkExec}
+     *         method doesn't allow creation of the subprocess, or
      *
-     * @see     Runtime#exec(String[], String[], java.io.File)
-     * @see     SecurityManager#checkExec(String)
+     *         <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
+     *
+     *         <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 {
         // 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/src/share/classes/sun/misc/JavaIOFileDescriptorAccess.java	Mon Mar 10 14:32:51 2008 -0700
+++ b/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/src/solaris/classes/java/io/FileDescriptor.java	Mon Mar 10 14:32:51 2008 -0700
+++ b/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/src/solaris/classes/java/lang/ProcessImpl.java	Mon Mar 10 14:32:51 2008 -0700
+++ b/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/src/solaris/classes/java/lang/UNIXProcess.java.linux	Mon Mar 10 14:32:51 2008 -0700
+++ b/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));
+                    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;
                         }
-                        });
+
+                        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/src/solaris/classes/java/lang/UNIXProcess.java.solaris	Mon Mar 10 14:32:51 2008 -0700
+++ b/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);
+            }
 
-	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;
-	    }
-	});
+            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/src/solaris/native/java/lang/UNIXProcess_md.c	Mon Mar 10 14:32:51 2008 -0700
+++ b/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/src/windows/classes/java/io/FileDescriptor.java	Mon Mar 10 14:32:51 2008 -0700
+++ b/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/src/windows/classes/java/lang/ProcessImpl.java	Mon Mar 10 14:32:51 2008 -0700
+++ b/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/src/windows/native/java/lang/ProcessImpl_md.c	Mon Mar 10 14:32:51 2008 -0700
+++ b/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;
 
-    SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE);
-    SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE);
-    SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE);
+    sa.nLength = sizeof(sa);
+    sa.lpSecurityDescriptor = 0;
+    sa.bInheritHandle = TRUE;
 
-    if (redirectErrorStream)
-        SetHandleInformation(errWrite, HANDLE_FLAG_INHERIT, FALSE);
+    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);
+
+    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)
         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/test/java/lang/ProcessBuilder/Basic.java	Mon Mar 10 14:32:51 2008 -0700
+++ b/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;