OpenJDK / jdk7 / jdk7 / jdk
changeset 30:c1a7b8f2c1bc
Merge
author | lmalvent |
---|---|
date | Mon, 10 Mar 2008 23:31:50 +0100 |
parents | 3c75107c46a4 bfed8f5f6345 |
children | 7618b0596aab |
files | |
diffstat | 23 files changed, 1870 insertions(+), 608 deletions(-) [+] |
line wrap: on
line diff
--- a/make/java/java/mapfile-vers Mon Mar 10 23:13:31 2008 +0100 +++ b/make/java/java/mapfile-vers Mon Mar 10 23:31:50 2008 +0100 @@ -85,7 +85,6 @@ Java_java_io_FileOutputStream_close0; Java_java_io_FileOutputStream_initIDs; Java_java_io_FileOutputStream_open; - Java_java_io_FileOutputStream_openAppend; Java_java_io_FileOutputStream_write; Java_java_io_FileOutputStream_writeBytes; Java_java_io_FileSystem_getFileSystem;
--- a/src/share/classes/java/io/FileInputStream.java Mon Mar 10 23:13:31 2008 +0100 +++ b/src/share/classes/java/io/FileInputStream.java Mon Mar 10 23:31:50 2008 +0100 @@ -48,15 +48,15 @@ class FileInputStream extends InputStream { /* File Descriptor - handle to the open file */ - private FileDescriptor fd; + private final FileDescriptor fd; private FileChannel channel = null; - private Object closeLock = new Object(); + private final Object closeLock = new Object(); private volatile boolean closed = false; - private static ThreadLocal<Boolean> runningFinalize = - new ThreadLocal<Boolean>(); + private static final ThreadLocal<Boolean> runningFinalize = + new ThreadLocal<Boolean>(); private static boolean isRunningFinalize() { Boolean val; @@ -151,7 +151,7 @@ * is thrown. * <p> * This constructor does not throw an exception if <code>fdObj</code> - * is {link java.io.FileDescriptor#valid() invalid}. + * is {@link java.io.FileDescriptor#valid() invalid}. * However, if the methods are invoked on the resulting stream to attempt * I/O on the stream, an <code>IOException</code> is thrown. * @@ -389,7 +389,7 @@ * @see java.io.FileInputStream#close() */ protected void finalize() throws IOException { - if ((fd != null) && (fd != fd.in)) { + if ((fd != null) && (fd != FileDescriptor.in)) { /* * Finalizer should not release the FileDescriptor if another
--- a/src/share/classes/java/io/FileOutputStream.java Mon Mar 10 23:13:31 2008 +0100 +++ b/src/share/classes/java/io/FileOutputStream.java Mon Mar 10 23:31:50 2008 +0100 @@ -52,20 +52,16 @@ class FileOutputStream extends OutputStream { /** - * The system dependent file descriptor. The value is - * 1 more than actual file descriptor. This means that - * the default value 0 indicates that the file is not open. + * The system dependent file descriptor. */ - private FileDescriptor fd; + private final FileDescriptor fd; private FileChannel channel= null; - private boolean append = false; - - private Object closeLock = new Object(); + private final Object closeLock = new Object(); private volatile boolean closed = false; - private static ThreadLocal<Boolean> runningFinalize = - new ThreadLocal<Boolean>(); + private static final ThreadLocal<Boolean> runningFinalize = + new ThreadLocal<Boolean>(); private static boolean isRunningFinalize() { Boolean val; @@ -75,7 +71,7 @@ } /** - * Creates an output file stream to write to the file with the + * Creates a file output stream to write to the file with the * specified name. A new <code>FileDescriptor</code> object is * created to represent this file connection. * <p> @@ -100,8 +96,8 @@ } /** - * Creates an output file stream to write to the file with the specified - * <code>name</code>. If the second argument is <code>true</code>, then + * Creates a file output stream to write to the file with the specified + * name. If the second argument is <code>true</code>, then * bytes will be written to the end of the file rather than the beginning. * A new <code>FileDescriptor</code> object is created to represent this * file connection. @@ -202,16 +198,11 @@ } fd = new FileDescriptor(); fd.incrementAndGetUseCount(); - this.append = append; - if (append) { - openAppend(name); - } else { - open(name); - } + open(name, append); } /** - * Creates an output file stream to write to the specified file + * Creates a file output stream to write to the specified file * descriptor, which represents an existing connection to an actual * file in the file system. * <p> @@ -223,7 +214,7 @@ * is thrown. * <p> * This constructor does not throw an exception if <code>fdObj</code> - * is {link java.io.FileDescriptor#valid() invalid}. + * is {@link java.io.FileDescriptor#valid() invalid}. * However, if the methods are invoked on the resulting stream to attempt * I/O on the stream, an <code>IOException</code> is thrown. * @@ -252,16 +243,12 @@ } /** - * Opens a file, with the specified name, for writing. + * Opens a file, with the specified name, for overwriting or appending. * @param name name of file to be opened + * @param append whether the file is to be opened in append mode */ - private native void open(String name) throws FileNotFoundException; - - /** - * Opens a file, with the specified name, for appending. - * @param name name of file to be opened - */ - private native void openAppend(String name) throws FileNotFoundException; + private native void open(String name, boolean append) + throws FileNotFoundException; /** * Writes the specified byte to this file output stream. Implements @@ -385,7 +372,7 @@ public FileChannel getChannel() { synchronized (this) { if (channel == null) { - channel = FileChannelImpl.open(fd, false, true, this, append); + channel = FileChannelImpl.open(fd, false, true, this); /* * Increment fd's use count. Invoking the channel's close() @@ -408,7 +395,7 @@ */ protected void finalize() throws IOException { if (fd != null) { - if (fd == fd.out || fd == fd.err) { + if (fd == FileDescriptor.out || fd == FileDescriptor.err) { flush(); } else {
--- a/src/share/classes/java/lang/Process.java Mon Mar 10 23:13:31 2008 +0100 +++ b/src/share/classes/java/lang/Process.java Mon Mar 10 23:31:50 2008 +0100 @@ -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 23:13:31 2008 +0100 +++ b/src/share/classes/java/lang/ProcessBuilder.java Mon Mar 10 23:31:50 2008 +0100 @@ -1,5 +1,5 @@ /* - * Copyright 2003-2006 Sun Microsystems, Inc. All Rights Reserved. + * Copyright 2003-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -27,6 +27,10 @@ import java.io.File; import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.FileOutputStream; +import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -34,7 +38,7 @@ /** * This class is used to create operating system processes. * - * <p>Each <code>ProcessBuilder</code> instance manages a collection + * <p>Each {@code ProcessBuilder} instance manages a collection * of process attributes. The {@link #start()} method creates a new * {@link Process} instance with those attributes. The {@link * #start()} method can be invoked repeatedly from the same instance @@ -59,19 +63,64 @@ * * <li>a <i>working directory</i>. The default value is the current * working directory of the current process, usually the directory - * named by the system property <code>user.dir</code>. + * named by the system property {@code user.dir}. + * + * <li><a name="redirect-input">a source of <i>standard input</i>. + * By default, the subprocess reads input from a pipe. Java code + * can access this pipe via the output stream returned by + * {@link Process#getOutputStream()}. However, standard input may + * be redirected to another source using + * {@link #redirectInput(Redirect) redirectInput}. + * In this case, {@link Process#getOutputStream()} will return a + * <i>null output stream</i>, for which: + * + * <ul> + * <li>the {@link OutputStream#write(int) write} methods always + * throw {@code IOException} + * <li>the {@link OutputStream#close() close} method does nothing + * </ul> + * + * <li><a name="redirect-output">a destination for <i>standard output</i> + * and <i>standard error</i>. By default, the subprocess writes standard + * output and standard error to pipes. Java code can access these pipes + * via the input streams returned by {@link Process#getInputStream()} and + * {@link Process#getErrorStream()}. However, standard output and + * standard error may be redirected to other destinations using + * {@link #redirectOutput(Redirect) redirectOutput} and + * {@link #redirectError(Redirect) redirectError}. + * In this case, {@link Process#getInputStream()} and/or + * {@link Process#getErrorStream()} will return a <i>null input + * stream</i>, for which: + * + * <ul> + * <li>the {@link InputStream#read() read} methods always return + * {@code -1} + * <li>the {@link InputStream#available() available} method always returns + * {@code 0} + * <li>the {@link InputStream#close() close} method does nothing + * </ul> * * <li>a <i>redirectErrorStream</i> property. Initially, this property - * is <code>false</code>, meaning that the standard output and error + * is {@code false}, meaning that the standard output and error * output of a subprocess are sent to two separate streams, which can * be accessed using the {@link Process#getInputStream()} and {@link - * Process#getErrorStream()} methods. If the value is set to - * <code>true</code>, the standard error is merged with the standard - * output. This makes it easier to correlate error messages with the - * corresponding output. In this case, the merged data can be read - * from the stream returned by {@link Process#getInputStream()}, while - * reading from the stream returned by {@link - * Process#getErrorStream()} will get an immediate end of file. + * Process#getErrorStream()} methods. + * + * <p>If the value is set to {@code true}, then: + * + * <ul> + * <li>standard error is merged with the standard output and always sent + * to the same destination (this makes it easier to correlate error + * messages with the corresponding output) + * <li>the common destination of standard error and standard output can be + * redirected using + * {@link #redirectOutput(Redirect) redirectOutput} + * <li>any redirection set by the + * {@link #redirectError(Redirect) redirectError} + * method is ignored when creating a subprocess + * <li>the stream returned from {@link Process#getErrorStream()} will + * always be a <a href="#redirect-output">null input stream</a> + * </ul> * * </ul> * @@ -87,34 +136,43 @@ * is invoked. * * <p><strong>Note that this class is not synchronized.</strong> - * If multiple threads access a <code>ProcessBuilder</code> instance + * If multiple threads access a {@code ProcessBuilder} instance * concurrently, and at least one of the threads modifies one of the * attributes structurally, it <i>must</i> be synchronized externally. * * <p>Starting a new process which uses the default working directory * and environment is easy: * - * <blockquote><pre> + * <pre> {@code * Process p = new ProcessBuilder("myCommand", "myArg").start(); - * </pre></blockquote> + * }</pre> * * <p>Here is an example that starts a process with a modified working - * directory and environment: + * directory and environment, and redirects standard output and error + * to be appended to a log file: * - * <blockquote><pre> - * ProcessBuilder pb = new ProcessBuilder("myCommand", "myArg1", "myArg2"); - * Map<String, String> env = pb.environment(); + * <pre> {@code + * ProcessBuilder pb = + * new ProcessBuilder("myCommand", "myArg1", "myArg2"); + * Map<String, String> env = pb.environment(); * env.put("VAR1", "myValue"); * env.remove("OTHERVAR"); * env.put("VAR2", env.get("VAR1") + "suffix"); * pb.directory(new File("myDir")); + * File log = new File("log"); + * pb.redirectErrorStream(true); + * pb.redirectOutput(Redirect.appendTo(log)); * Process p = pb.start(); - * </pre></blockquote> + * assert pb.redirectInput() == Redirect.PIPE; + * assert pb.redirectOutput().file() == log; + * assert p.getInputStream().read() == -1; + * }</pre> * * <p>To start a process with an explicit set of environment * variables, first call {@link java.util.Map#clear() Map.clear()} * before adding environment variables. * + * @author Martin Buchholz * @since 1.5 */ @@ -124,20 +182,19 @@ private File directory; private Map<String,String> environment; private boolean redirectErrorStream; + private Redirect[] redirects; /** * Constructs a process builder with the specified operating * system program and arguments. This constructor does <i>not</i> - * make a copy of the <code>command</code> list. Subsequent + * make a copy of the {@code command} list. Subsequent * updates to the list will be reflected in the state of the * process builder. It is not checked whether - * <code>command</code> corresponds to a valid operating system - * command.</p> + * {@code command} corresponds to a valid operating system + * command. * - * @param command The list containing the program and its arguments - * - * @throws NullPointerException - * If the argument is <code>null</code> + * @param command the list containing the program and its arguments + * @throws NullPointerException if the argument is null */ public ProcessBuilder(List<String> command) { if (command == null) @@ -149,12 +206,12 @@ * Constructs a process builder with the specified operating * system program and arguments. This is a convenience * constructor that sets the process builder's command to a string - * list containing the same strings as the <code>command</code> + * list containing the same strings as the {@code command} * array, in the same order. It is not checked whether - * <code>command</code> corresponds to a valid operating system - * command.</p> + * {@code command} corresponds to a valid operating system + * command. * - * @param command A string array containing the program and its arguments + * @param command a string array containing the program and its arguments */ public ProcessBuilder(String... command) { this.command = new ArrayList<String>(command.length); @@ -165,16 +222,15 @@ /** * Sets this process builder's operating system program and * arguments. This method does <i>not</i> make a copy of the - * <code>command</code> list. Subsequent updates to the list will + * {@code command} list. Subsequent updates to the list will * be reflected in the state of the process builder. It is not - * checked whether <code>command</code> corresponds to a valid - * operating system command.</p> + * checked whether {@code command} corresponds to a valid + * operating system command. * - * @param command The list containing the program and its arguments - * @return This process builder + * @param command the list containing the program and its arguments + * @return this process builder * - * @throws NullPointerException - * If the argument is <code>null</code> + * @throws NullPointerException if the argument is null */ public ProcessBuilder command(List<String> command) { if (command == null) @@ -187,12 +243,12 @@ * Sets this process builder's operating system program and * arguments. This is a convenience method that sets the command * to a string list containing the same strings as the - * <code>command</code> array, in the same order. It is not - * checked whether <code>command</code> corresponds to a valid - * operating system command.</p> + * {@code command} array, in the same order. It is not + * checked whether {@code command} corresponds to a valid + * operating system command. * - * @param command A string array containing the program and its arguments - * @return This process builder + * @param command a string array containing the program and its arguments + * @return this process builder */ public ProcessBuilder command(String... command) { this.command = new ArrayList<String>(command.length); @@ -205,9 +261,9 @@ * Returns this process builder's operating system program and * arguments. The returned list is <i>not</i> a copy. Subsequent * updates to the list will be reflected in the state of this - * process builder.</p> + * process builder. * - * @return This process builder's program and its arguments + * @return this process builder's program and its arguments */ public List<String> command() { return command; @@ -225,10 +281,10 @@ * <p>The returned object may be modified using ordinary {@link * java.util.Map Map} operations. These modifications will be * visible to subprocesses started via the {@link #start()} - * method. Two <code>ProcessBuilder</code> instances always + * method. Two {@code ProcessBuilder} instances always * contain independent process environments, so changes to the * returned map will never be reflected in any other - * <code>ProcessBuilder</code> instance or the values returned by + * {@code ProcessBuilder} instance or the values returned by * {@link System#getenv System.getenv}. * * <p>If the system does not support environment variables, an @@ -262,25 +318,24 @@ * <p>The returned map is typically case-sensitive on all platforms. * * <p>If a security manager exists, its - * {@link SecurityManager#checkPermission checkPermission} - * method is called with a - * <code>{@link RuntimePermission}("getenv.*")</code> - * permission. This may result in a {@link SecurityException} being - * thrown. + * {@link SecurityManager#checkPermission checkPermission} method + * is called with a + * {@link RuntimePermission}{@code ("getenv.*")} permission. + * This may result in a {@link SecurityException} being thrown. * * <p>When passing information to a Java subprocess, * <a href=System.html#EnvironmentVSSystemProperties>system properties</a> - * are generally preferred over environment variables.</p> + * are generally preferred over environment variables. * - * @return This process builder's environment + * @return this process builder's environment * - * @throws SecurityException - * If a security manager exists and its - * {@link SecurityManager#checkPermission checkPermission} - * method doesn't allow access to the process environment + * @throws SecurityException + * if a security manager exists and its + * {@link SecurityManager#checkPermission checkPermission} + * method doesn't allow access to the process environment * - * @see Runtime#exec(String[],String[],java.io.File) - * @see System#getenv() + * @see Runtime#exec(String[],String[],java.io.File) + * @see System#getenv() */ public Map<String,String> environment() { SecurityManager security = System.getSecurityManager(); @@ -328,12 +383,12 @@ * * Subprocesses subsequently started by this object's {@link * #start()} method will use this as their working directory. - * The returned value may be <code>null</code> -- this means to use + * The returned value may be {@code null} -- this means to use * the working directory of the current Java process, usually the - * directory named by the system property <code>user.dir</code>, - * as the working directory of the child process.</p> + * directory named by the system property {@code user.dir}, + * as the working directory of the child process. * - * @return This process builder's working directory + * @return this process builder's working directory */ public File directory() { return directory; @@ -344,50 +399,522 @@ * * Subprocesses subsequently started by this object's {@link * #start()} method will use this as their working directory. - * The argument may be <code>null</code> -- this means to use the + * The argument may be {@code null} -- this means to use the * working directory of the current Java process, usually the - * directory named by the system property <code>user.dir</code>, - * as the working directory of the child process.</p> + * directory named by the system property {@code user.dir}, + * as the working directory of the child process. * - * @param directory The new working directory - * @return This process builder + * @param directory the new working directory + * @return this process builder */ public ProcessBuilder directory(File directory) { this.directory = directory; return this; } + // ---------------- I/O Redirection ---------------- + + /** + * Implements a <a href="#redirect-output">null input stream</a>. + */ + static class NullInputStream extends InputStream { + public int read() { return -1; } + public int available() { return 0; } + } + + /** + * Implements a <a href="#redirect-input">null output stream</a>. + */ + static class NullOutputStream extends OutputStream { + public void write(int b) throws IOException { + throw new IOException("Stream closed"); + } + } + + /** + * Represents a source of subprocess input or a destination of + * subprocess output. + * + * Each {@code Redirect} instance is one of the following: + * + * <ul> + * <li>the special value {@link #PIPE Redirect.PIPE} + * <li>the special value {@link #INHERIT Redirect.INHERIT} + * <li>a redirection to read from a file, created by an invocation of + * {@link Redirect#from Redirect.from(File)} + * <li>a redirection to write to a file, created by an invocation of + * {@link Redirect#to Redirect.to(File)} + * <li>a redirection to append to a file, created by an invocation of + * {@link Redirect#appendTo Redirect.appendTo(File)} + * </ul> + * + * <p>Each of the above categories has an associated unique + * {@link Type Type}. + * + * @since 1.7 + */ + public static abstract class Redirect { + /** + * The type of a {@link Redirect}. + */ + public enum Type { + /** + * The type of {@link Redirect#PIPE Redirect.PIPE}. + */ + PIPE, + + /** + * The type of {@link Redirect#INHERIT Redirect.INHERIT}. + */ + INHERIT, + + /** + * The type of redirects returned from + * {@link Redirect#from Redirect.from(File)}. + */ + READ, + + /** + * The type of redirects returned from + * {@link Redirect#to Redirect.to(File)}. + */ + WRITE, + + /** + * The type of redirects returned from + * {@link Redirect#appendTo Redirect.appendTo(File)}. + */ + APPEND + }; + + /** + * Returns the type of this {@code Redirect}. + * @return the type of this {@code Redirect} + */ + public abstract Type type(); + + /** + * Indicates that subprocess I/O will be connected to the + * current Java process over a pipe. + * + * This is the default handling of subprocess standard I/O. + * + * <p>It will always be true that + * <pre> {@code + * Redirect.PIPE.file() == null && + * Redirect.PIPE.type() == Redirect.Type.PIPE + * }</pre> + */ + public static final Redirect PIPE = new Redirect() { + public Type type() { return Type.PIPE; } + public String toString() { return type().toString(); }}; + + /** + * Indicates that subprocess I/O source or destination will be the + * same as those of the current process. This is the normal + * behavior of most operating system command interpreters (shells). + * + * <p>It will always be true that + * <pre> {@code + * Redirect.INHERIT.file() == null && + * Redirect.INHERIT.type() == Redirect.Type.INHERIT + * }</pre> + */ + public static final Redirect INHERIT = new Redirect() { + public Type type() { return Type.INHERIT; } + public String toString() { return type().toString(); }}; + + /** + * Returns the {@link File} source or destination associated + * with this redirect, or {@code null} if there is no such file. + * + * @return the file associated with this redirect, + * or {@code null} if there is no such file + */ + public File file() { return null; } + + FileOutputStream toFileOutputStream() throws IOException { + throw new UnsupportedOperationException(); + } + + /** + * Returns a redirect to read from the specified file. + * + * <p>It will always be true that + * <pre> {@code + * Redirect.from(file).file() == file && + * Redirect.from(file).type() == Redirect.Type.READ + * }</pre> + * + * @throws NullPointerException if the specified file is null + * @return a redirect to read from the specified file + */ + public static Redirect from(final File file) { + if (file == null) + throw new NullPointerException(); + return new Redirect() { + public Type type() { return Type.READ; } + public File file() { return file; } + public String toString() { + return "redirect to read from file \"" + file + "\""; + } + }; + } + + /** + * Returns a redirect to write to the specified file. + * If the specified file exists when the subprocess is started, + * its previous contents will be discarded. + * + * <p>It will always be true that + * <pre> {@code + * Redirect.to(file).file() == file && + * Redirect.to(file).type() == Redirect.Type.WRITE + * }</pre> + * + * @throws NullPointerException if the specified file is null + * @return a redirect to write to the specified file + */ + public static Redirect to(final File file) { + if (file == null) + throw new NullPointerException(); + return new Redirect() { + public Type type() { return Type.WRITE; } + public File file() { return file; } + public String toString() { + return "redirect to write to file \"" + file + "\""; + } + FileOutputStream toFileOutputStream() throws IOException { + return new FileOutputStream(file, false); + } + }; + } + + /** + * Returns a redirect to append to the specified file. + * Each write operation first advances the position to the + * end of the file and then writes the requested data. + * Whether the advancement of the position and the writing + * of the data are done in a single atomic operation is + * system-dependent and therefore unspecified. + * + * <p>It will always be true that + * <pre> {@code + * Redirect.appendTo(file).file() == file && + * Redirect.appendTo(file).type() == Redirect.Type.APPEND + * }</pre> + * + * @throws NullPointerException if the specified file is null + * @return a redirect to append to the specified file + */ + public static Redirect appendTo(final File file) { + if (file == null) + throw new NullPointerException(); + return new Redirect() { + public Type type() { return Type.APPEND; } + public File file() { return file; } + public String toString() { + return "redirect to append to file \"" + file + "\""; + } + FileOutputStream toFileOutputStream() throws IOException { + return new FileOutputStream(file, true); + } + }; + } + + /** + * Compares the specified object with this {@code Redirect} for + * equality. Returns {@code true} if and only if the two + * objects are identical or both objects are {@code Redirect} + * instances of the same type associated with non-null equal + * {@code File} instances. + */ + public boolean equals(Object obj) { + if (obj == this) + return true; + if (! (obj instanceof Redirect)) + return false; + Redirect r = (Redirect) obj; + if (r.type() != this.type()) + return false; + assert this.file() != null; + return this.file().equals(r.file()); + } + + /** + * Returns a hash code value for this {@code Redirect}. + * @return a hash code value for this {@code Redirect} + */ + public int hashCode() { + File file = file(); + if (file == null) + return super.hashCode(); + else + return file.hashCode(); + } + + /** + * No public constructors. Clients must use predefined + * static {@code Redirect} instances or factory methods. + */ + private Redirect() {} + } + + private Redirect[] redirects() { + if (redirects == null) + redirects = new Redirect[] { + Redirect.PIPE, Redirect.PIPE, Redirect.PIPE + }; + return redirects; + } + + /** + * Sets this process builder's standard input source. + * + * Subprocesses subsequently started by this object's {@link #start()} + * method obtain their standard input from this source. + * + * <p>If the source is {@link Redirect#PIPE Redirect.PIPE} + * (the initial value), then the standard input of a + * subprocess can be written to using the output stream + * returned by {@link Process#getOutputStream()}. + * If the source is set to any other value, then + * {@link Process#getOutputStream()} will return a + * <a href="#redirect-input">null output stream</a>. + * + * @param source the new standard input source + * @return this process builder + * @throws IllegalArgumentException + * if the redirect does not correspond to a valid source + * of data, that is, has type + * {@link Redirect.Type#WRITE WRITE} or + * {@link Redirect.Type#APPEND APPEND} + * @since 1.7 + */ + public ProcessBuilder redirectInput(Redirect source) { + if (source.type() == Redirect.Type.WRITE || + source.type() == Redirect.Type.APPEND) + throw new IllegalArgumentException( + "Redirect invalid for reading: " + source); + redirects()[0] = source; + return this; + } + + /** + * Sets this process builder's standard output destination. + * + * Subprocesses subsequently started by this object's {@link #start()} + * method send their standard output to this destination. + * + * <p>If the destination is {@link Redirect#PIPE Redirect.PIPE} + * (the initial value), then the standard output of a subprocess + * can be read using the input stream returned by {@link + * Process#getInputStream()}. + * If the destination is set to any other value, then + * {@link Process#getInputStream()} will return a + * <a href="#redirect-output">null input stream</a>. + * + * @param destination the new standard output destination + * @return this process builder + * @throws IllegalArgumentException + * if the redirect does not correspond to a valid + * destination of data, that is, has type + * {@link Redirect.Type#READ READ} + * @since 1.7 + */ + public ProcessBuilder redirectOutput(Redirect destination) { + if (destination.type() == Redirect.Type.READ) + throw new IllegalArgumentException( + "Redirect invalid for writing: " + destination); + redirects()[1] = destination; + return this; + } + + /** + * Sets this process builder's standard error destination. + * + * Subprocesses subsequently started by this object's {@link #start()} + * method send their standard error to this destination. + * + * <p>If the destination is {@link Redirect#PIPE Redirect.PIPE} + * (the initial value), then the error output of a subprocess + * can be read using the input stream returned by {@link + * Process#getErrorStream()}. + * If the destination is set to any other value, then + * {@link Process#getErrorStream()} will return a + * <a href="#redirect-output">null input stream</a>. + * + * <p>If the {@link #redirectErrorStream redirectErrorStream} + * attribute has been set {@code true}, then the redirection set + * by this method has no effect. + * + * @param destination the new standard error destination + * @return this process builder + * @throws IllegalArgumentException + * if the redirect does not correspond to a valid + * destination of data, that is, has type + * {@link Redirect.Type#READ READ} + * @since 1.7 + */ + public ProcessBuilder redirectError(Redirect destination) { + if (destination.type() == Redirect.Type.READ) + throw new IllegalArgumentException( + "Redirect invalid for writing: " + destination); + redirects()[2] = destination; + return this; + } + + /** + * Sets this process builder's standard input source to a file. + * + * <p>This is a convenience method. An invocation of the form + * {@code redirectInput(file)} + * behaves in exactly the same way as the invocation + * {@link #redirectInput(Redirect) redirectInput} + * {@code (Redirect.from(file))}. + * + * @param file the new standard input source + * @return this process builder + * @since 1.7 + */ + public ProcessBuilder redirectInput(File file) { + return redirectInput(Redirect.from(file)); + } + + /** + * Sets this process builder's standard output destination to a file. + * + * <p>This is a convenience method. An invocation of the form + * {@code redirectOutput(file)} + * behaves in exactly the same way as the invocation + * {@link #redirectOutput(Redirect) redirectOutput} + * {@code (Redirect.to(file))}. + * + * @param file the new standard output destination + * @return this process builder + * @since 1.7 + */ + public ProcessBuilder redirectOutput(File file) { + return redirectOutput(Redirect.to(file)); + } + + /** + * Sets this process builder's standard error destination to a file. + * + * <p>This is a convenience method. An invocation of the form + * {@code redirectError(file)} + * behaves in exactly the same way as the invocation + * {@link #redirectError(Redirect) redirectError} + * {@code (Redirect.to(file))}. + * + * @param file the new standard error destination + * @return this process builder + * @since 1.7 + */ + public ProcessBuilder redirectError(File file) { + return redirectError(Redirect.to(file)); + } + + /** + * Returns this process builder's standard input source. + * + * Subprocesses subsequently started by this object's {@link #start()} + * method obtain their standard input from this source. + * The initial value is {@link Redirect#PIPE Redirect.PIPE}. + * + * @return this process builder's standard input source + * @since 1.7 + */ + public Redirect redirectInput() { + return (redirects == null) ? Redirect.PIPE : redirects[0]; + } + + /** + * Returns this process builder's standard output destination. + * + * Subprocesses subsequently started by this object's {@link #start()} + * method redirect their standard output to this destination. + * The initial value is {@link Redirect#PIPE Redirect.PIPE}. + * + * @return this process builder's standard output destination + * @since 1.7 + */ + public Redirect redirectOutput() { + return (redirects == null) ? Redirect.PIPE : redirects[1]; + } + + /** + * Returns this process builder's standard error destination. + * + * Subprocesses subsequently started by this object's {@link #start()} + * method redirect their standard error to this destination. + * The initial value is {@link Redirect#PIPE Redirect.PIPE}. + * + * @return this process builder's standard error destination + * @since 1.7 + */ + public Redirect redirectError() { + return (redirects == null) ? Redirect.PIPE : redirects[2]; + } + + /** + * Sets the source and destination for subprocess standard I/O + * to be the same as those of the current Java process. + * + * <p>This is a convenience method. An invocation of the form + * <pre> {@code + * pb.inheritIO() + * }</pre> + * behaves in exactly the same way as the invocation + * <pre> {@code + * pb.redirectInput(Redirect.INHERIT) + * .redirectOutput(Redirect.INHERIT) + * .redirectError(Redirect.INHERIT) + * }</pre> + * + * This gives behavior equivalent to most operating system + * command interpreters, or the standard C library function + * {@code system()}. + * + * @return this process builder + * @since 1.7 + */ + public ProcessBuilder inheritIO() { + Arrays.fill(redirects(), Redirect.INHERIT); + return this; + } + /** * Tells whether this process builder merges standard error and * standard output. * - * <p>If this property is <code>true</code>, then any error output + * <p>If this property is {@code true}, then any error output * generated by subprocesses subsequently started by this object's * {@link #start()} method will be merged with the standard * output, so that both can be read using the * {@link Process#getInputStream()} method. This makes it easier * to correlate error messages with the corresponding output. - * The initial value is <code>false</code>.</p> + * The initial value is {@code false}. * - * @return This process builder's <code>redirectErrorStream</code> property + * @return this process builder's {@code redirectErrorStream} property */ public boolean redirectErrorStream() { return redirectErrorStream; } /** - * Sets this process builder's <code>redirectErrorStream</code> property. + * Sets this process builder's {@code redirectErrorStream} property. * - * <p>If this property is <code>true</code>, then any error output + * <p>If this property is {@code true}, then any error output * generated by subprocesses subsequently started by this object's * {@link #start()} method will be merged with the standard * output, so that both can be read using the * {@link Process#getInputStream()} method. This makes it easier * to correlate error messages with the corresponding output. - * The initial value is <code>false</code>.</p> + * The initial value is {@code false}. * - * @param redirectErrorStream The new property value - * @return This process builder + * @param redirectErrorStream the new property value + * @return this process builder */ public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) { this.redirectErrorStream = redirectErrorStream; @@ -410,7 +937,7 @@ * <p>If there is a security manager, its * {@link SecurityManager#checkExec checkExec} * method is called with the first component of this object's - * <code>command</code> array as its argument. This may result in + * {@code command} array as its argument. This may result in * a {@link SecurityException} being thrown. * * <p>Starting an operating system process is highly system-dependent. @@ -426,26 +953,42 @@ * subclass of {@link IOException}. * * <p>Subsequent modifications to this process builder will not - * affect the returned {@link Process}.</p> + * affect the returned {@link Process}. * - * @return A new {@link Process} object for managing the subprocess + * @return a new {@link Process} object for managing the subprocess + * + * @throws NullPointerException + * if an element of the command list is null * - * @throws NullPointerException - * If an element of the command list is null + * @throws IndexOutOfBoundsException + * if the command is an empty list (has size {@code 0}) * - * @throws IndexOutOfBoundsException - * If the command is an empty list (has size <code>0</code>) + * @throws SecurityException + * if a security manager exists and + * <ul> + * + * <li>its + * {@link SecurityManager#checkExec checkExec} + * method doesn't allow creation of the subprocess, or * - * @throws SecurityException - * If a security manager exists and its - * {@link SecurityManager#checkExec checkExec} - * method doesn't allow creation of the subprocess + * <li>the standard input to the subprocess was + * {@linkplain #redirectInput redirected from a file} + * and the security manager's + * {@link SecurityManager#checkRead checkRead} method + * denies read access to the file, or * - * @throws IOException - * If an I/O error occurs + * <li>the standard output or standard error of the + * subprocess was + * {@linkplain #redirectOutput redirected to a file} + * and the security manager's + * {@link SecurityManager#checkWrite checkWrite} method + * denies write access to the file * - * @see Runtime#exec(String[], String[], java.io.File) - * @see SecurityManager#checkExec(String) + * </ul> + * + * @throws IOException if an I/O error occurs + * + * @see Runtime#exec(String[], String[], java.io.File) */ public Process start() throws IOException { // Must convert to array first -- a malicious user-supplied @@ -467,6 +1010,7 @@ return ProcessImpl.start(cmdarray, environment, dir, + redirects, redirectErrorStream); } catch (IOException e) { // It's much easier for us to create a high-quality error
--- a/src/share/classes/sun/misc/JavaIOFileDescriptorAccess.java Mon Mar 10 23:13:31 2008 +0100 +++ b/src/share/classes/sun/misc/JavaIOFileDescriptorAccess.java Mon Mar 10 23:31:50 2008 +0100 @@ -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/share/classes/sun/nio/ch/FileChannelImpl.java Mon Mar 10 23:13:31 2008 +0100 +++ b/src/share/classes/sun/nio/ch/FileChannelImpl.java Mon Mar 10 23:31:50 2008 +0100 @@ -52,39 +52,37 @@ { // Used to make native read and write calls - private static NativeDispatcher nd; + private static final NativeDispatcher nd; // Memory allocation size for mapping buffers - private static long allocationGranularity; + private static final long allocationGranularity; // Cached field for MappedByteBuffer.isAMappedBuffer - private static Field isAMappedBufferField; + private static final Field isAMappedBufferField; // File descriptor - private FileDescriptor fd; + private final FileDescriptor fd; // File access mode (immutable) - private boolean writable; - private boolean readable; - private boolean appending; + private final boolean writable; + private final boolean readable; // Required to prevent finalization of creating stream (immutable) - private Object parent; + private final Object parent; // Thread-safe set of IDs of native threads, for signalling - private NativeThreadSet threads = new NativeThreadSet(2); + private final NativeThreadSet threads = new NativeThreadSet(2); // Lock for operations involving position and size - private Object positionLock = new Object(); + private final Object positionLock = new Object(); private FileChannelImpl(FileDescriptor fd, boolean readable, - boolean writable, Object parent, boolean append) + boolean writable, Object parent) { this.fd = fd; this.readable = readable; this.writable = writable; this.parent = parent; - this.appending = append; } // Invoked by getChannel() methods @@ -94,14 +92,7 @@ boolean readable, boolean writable, Object parent) { - return new FileChannelImpl(fd, readable, writable, parent, false); - } - - public static FileChannel open(FileDescriptor fd, - boolean readable, boolean writable, - Object parent, boolean append) - { - return new FileChannelImpl(fd, readable, writable, parent, append); + return new FileChannelImpl(fd, readable, writable, parent); } private void ensureOpen() throws IOException { @@ -134,15 +125,7 @@ // superclass AbstractInterruptibleChannel, but the isOpen logic in // that method will prevent this method from being reinvoked. // - if (parent instanceof FileInputStream) - ((FileInputStream)parent).close(); - else if (parent instanceof FileOutputStream) - ((FileOutputStream)parent).close(); - else if (parent instanceof RandomAccessFile) - ((RandomAccessFile)parent).close(); - else - assert false; - + ((java.io.Closeable)parent).close(); } else { nd.close(fd); } @@ -218,8 +201,6 @@ if (!isOpen()) return 0; ti = threads.add(); - if (appending) - position(size()); do { n = IOUtil.write(fd, src, -1, nd, positionLock); } while ((n == IOStatus.INTERRUPTED) && isOpen()); @@ -244,8 +225,6 @@ if (!isOpen()) return 0; ti = threads.add(); - if (appending) - position(size()); do { n = IOUtil.write(fd, srcs, nd); } while ((n == IOStatus.INTERRUPTED) && isOpen()); @@ -1051,7 +1030,7 @@ private FileKey fileKey; FileLockReference(FileLock referent, - ReferenceQueue queue, + ReferenceQueue<FileLock> queue, FileKey key) { super(referent, queue); this.fileKey = key; @@ -1073,7 +1052,7 @@ new ConcurrentHashMap<FileKey, ArrayList<FileLockReference>>(); // reference queue for cleared refs - private static ReferenceQueue queue = new ReferenceQueue(); + private static ReferenceQueue<FileLock> queue = new ReferenceQueue<FileLock>(); // the enclosing file channel private FileChannelImpl fci;
--- a/src/share/native/java/io/io_util.c Mon Mar 10 23:13:31 2008 +0100 +++ b/src/share/native/java/io/io_util.c Mon Mar 10 23:31:50 2008 +0100 @@ -40,7 +40,7 @@ char ret; FD fd = GET_FD(this, fid); if (fd == -1) { - JNU_ThrowIOException (env, "Stream Closed"); + JNU_ThrowIOException(env, "Stream Closed"); return -1; } nread = IO_Read(fd, &ret, 1); @@ -94,8 +94,8 @@ fd = GET_FD(this, fid); if (fd == -1) { - JNU_ThrowIOException (env, "Stream Closed"); - return -1; + JNU_ThrowIOException(env, "Stream Closed"); + return -1; } nread = IO_Read(fd, buf, len); @@ -121,7 +121,7 @@ int n; FD fd = GET_FD(this, fid); if (fd == -1) { - JNU_ThrowIOException (env, "Stream Closed"); + JNU_ThrowIOException(env, "Stream Closed"); return; } n = IO_Write(fd, &c, 1); @@ -172,8 +172,8 @@ while (len > 0) { fd = GET_FD(this, fid); if (fd == -1) { - JNU_ThrowIOException (env, "Stream Closed"); - return; + JNU_ThrowIOException(env, "Stream Closed"); + break; } n = IO_Write(fd, buf+off, len); if (n == JVM_IO_ERR) {
--- a/src/solaris/classes/java/io/FileDescriptor.java Mon Mar 10 23:13:31 2008 +0100 +++ b/src/solaris/classes/java/io/FileDescriptor.java Mon Mar 10 23:31:50 2008 +0100 @@ -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 23:13:31 2008 +0100 +++ b/src/solaris/classes/java/lang/ProcessImpl.java Mon Mar 10 23:31:50 2008 +0100 @@ -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 23:13:31 2008 +0100 +++ b/src/solaris/classes/java/lang/UNIXProcess.java.linux Mon Mar 10 23:31:50 2008 +0100 @@ -1,5 +1,5 @@ -/* - * Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved. +/* + * Copyright 1995-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -34,9 +34,9 @@ */ final class UNIXProcess extends Process { - private FileDescriptor stdin_fd; - private FileDescriptor stdout_fd; - private FileDescriptor stderr_fd; + private static final sun.misc.JavaIOFileDescriptorAccess fdAccess + = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); + private int pid; private int exitcode; private boolean hasExited; @@ -48,15 +48,26 @@ /* this is for the reaping thread */ private native int waitForProcessExit(int pid); + /** + * Create a process using fork(2) and exec(2). + * + * @param std_fds array of file descriptors. Indexes 0, 1, and + * 2 correspond to standard input, standard output and + * standard error, respectively. On input, a value of -1 + * means to create a pipe to connect child and parent + * processes. On output, a value which is not -1 is the + * parent pipe fd corresponding to the pipe which has + * been created. An element of this array is -1 on input + * if and only if it is <em>not</em> -1 on output. + * @return the pid of the subprocess + */ private native int forkAndExec(byte[] prog, - byte[] argBlock, int argc, - byte[] envBlock, int envc, - byte[] dir, - boolean redirectErrorStream, - FileDescriptor stdin_fd, - FileDescriptor stdout_fd, - FileDescriptor stderr_fd) - throws IOException; + byte[] argBlock, int argc, + byte[] envBlock, int envc, + byte[] dir, + int[] std_fds, + boolean redirectErrorStream) + throws IOException; /* In the process constructor we wait on this gate until the process */ /* has been created. Then we return from the constructor. */ @@ -97,67 +108,82 @@ } UNIXProcess(final byte[] prog, - final byte[] argBlock, final int argc, - final byte[] envBlock, final int envc, - final byte[] dir, - final boolean redirectErrorStream) + final byte[] argBlock, final int argc, + final byte[] envBlock, final int envc, + final byte[] dir, + final int[] std_fds, + final boolean redirectErrorStream) throws IOException { - stdin_fd = new FileDescriptor(); - stdout_fd = new FileDescriptor(); - stderr_fd = new FileDescriptor(); final Gate gate = new Gate(); - /* - * For each subprocess forked a corresponding reaper thread - * is started. That thread is the only thread which waits - * for the subprocess to terminate and it doesn't hold any - * locks while doing so. This design allows waitFor() and - * exitStatus() to be safely executed in parallel (and they - * need no native code). - */ + /* + * For each subprocess forked a corresponding reaper thread + * is started. That thread is the only thread which waits + * for the subprocess to terminate and it doesn't hold any + * locks while doing so. This design allows waitFor() and + * exitStatus() to be safely executed in parallel (and they + * need no native code). + */ - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Object run() { - Thread t = new Thread("process reaper") { - public void run() { + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction<Void>() { + public Void run() { + Thread t = new Thread("process reaper") { + public void run() { try { pid = forkAndExec(prog, - argBlock, argc, - envBlock, envc, - dir, - redirectErrorStream, - stdin_fd, stdout_fd, stderr_fd); + argBlock, argc, + envBlock, envc, + dir, + std_fds, + redirectErrorStream); } catch (IOException e) { gate.setException(e); /*remember to rethrow later*/ gate.exit(); return; } java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Object run() { - stdin_stream = new BufferedOutputStream(new - FileOutputStream(stdin_fd)); - stdout_stream = new BufferedInputStream(new - FileInputStream(stdout_fd)); - stderr_stream = new FileInputStream(stderr_fd); - return null; + new java.security.PrivilegedAction<Void>() { + public Void run() { + if (std_fds[0] == -1) + stdin_stream = new ProcessBuilder.NullOutputStream(); + else { + FileDescriptor stdin_fd = new FileDescriptor(); + fdAccess.set(stdin_fd, std_fds[0]); + stdin_stream = new BufferedOutputStream( + new FileOutputStream(stdin_fd)); + } + + if (std_fds[1] == -1) + stdout_stream = new ProcessBuilder.NullInputStream(); + else { + FileDescriptor stdout_fd = new FileDescriptor(); + fdAccess.set(stdout_fd, std_fds[1]); + stdout_stream = new BufferedInputStream( + new FileInputStream(stdout_fd)); } - }); + + if (std_fds[2] == -1) + stderr_stream = new ProcessBuilder.NullInputStream(); + else { + FileDescriptor stderr_fd = new FileDescriptor(); + fdAccess.set(stderr_fd, std_fds[2]); + stderr_stream = new FileInputStream(stderr_fd); + } + + return null; }}); gate.exit(); /* exit from constructor */ - int res = waitForProcessExit(pid); - synchronized (UNIXProcess.this) { - hasExited = true; - exitcode = res; - UNIXProcess.this.notifyAll(); - } - } - }; + int res = waitForProcessExit(pid); + synchronized (UNIXProcess.this) { + hasExited = true; + exitcode = res; + UNIXProcess.this.notifyAll(); + } + } + }; t.setDaemon(true); t.start(); - return null; - } - }); + return null; }}); gate.waitForExit(); IOException e = gate.getException(); if (e != null) @@ -165,43 +191,43 @@ } public OutputStream getOutputStream() { - return stdin_stream; + return stdin_stream; } public InputStream getInputStream() { - return stdout_stream; + return stdout_stream; } public InputStream getErrorStream() { - return stderr_stream; + return stderr_stream; } public synchronized int waitFor() throws InterruptedException { while (!hasExited) { - wait(); - } - return exitcode; + wait(); + } + return exitcode; } public synchronized int exitValue() { - if (!hasExited) { - throw new IllegalThreadStateException("process hasn't exited"); - } - return exitcode; + if (!hasExited) { + throw new IllegalThreadStateException("process hasn't exited"); + } + return exitcode; } private static native void destroyProcess(int pid); public void destroy() { - // There is a risk that pid will be recycled, causing us to - // kill the wrong process! So we only terminate processes - // that appear to still be running. Even with this check, - // there is an unavoidable race condition here, but the window - // is very small, and OSes try hard to not recycle pids too - // soon, so this is quite safe. - synchronized (this) { - if (!hasExited) - destroyProcess(pid); - } + // There is a risk that pid will be recycled, causing us to + // kill the wrong process! So we only terminate processes + // that appear to still be running. Even with this check, + // there is an unavoidable race condition here, but the window + // is very small, and OSes try hard to not recycle pids too + // soon, so this is quite safe. + synchronized (this) { + if (!hasExited) + destroyProcess(pid); + } try { stdin_stream.close(); stdout_stream.close(); @@ -215,6 +241,6 @@ private static native void initIDs(); static { - initIDs(); + initIDs(); } }
--- a/src/solaris/classes/java/lang/UNIXProcess.java.solaris Mon Mar 10 23:13:31 2008 +0100 +++ b/src/solaris/classes/java/lang/UNIXProcess.java.solaris Mon Mar 10 23:31:50 2008 +0100 @@ -1,5 +1,5 @@ -/* - * Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved. +/* + * Copyright 1995-2008 Sun Microsystems, Inc. All Rights Reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -33,129 +33,155 @@ */ final class UNIXProcess extends Process { - private FileDescriptor stdin_fd; - private FileDescriptor stdout_fd; - private FileDescriptor stderr_fd; - private int pid; + private static final sun.misc.JavaIOFileDescriptorAccess fdAccess + = sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess(); + + private final int pid; private int exitcode; private boolean hasExited; private OutputStream stdin_stream; - private BufferedInputStream stdout_stream; + private InputStream stdout_stream; private DeferredCloseInputStream stdout_inner_stream; - private DeferredCloseInputStream stderr_stream; + private InputStream stderr_stream; /* this is for the reaping thread */ private native int waitForProcessExit(int pid); + /** + * Create a process using fork(2) and exec(2). + * + * @param std_fds array of file descriptors. Indexes 0, 1, and + * 2 correspond to standard input, standard output and + * standard error, respectively. On input, a value of -1 + * means to create a pipe to connect child and parent + * processes. On output, a value which is not -1 is the + * parent pipe fd corresponding to the pipe which has + * been created. An element of this array is -1 on input + * if and only if it is <em>not</em> -1 on output. + * @return the pid of the subprocess + */ private native int forkAndExec(byte[] prog, - byte[] argBlock, int argc, - byte[] envBlock, int envc, - byte[] dir, - boolean redirectErrorStream, - FileDescriptor stdin_fd, - FileDescriptor stdout_fd, - FileDescriptor stderr_fd) - throws IOException; + byte[] argBlock, int argc, + byte[] envBlock, int envc, + byte[] dir, + int[] std_fds, + boolean redirectErrorStream) + throws IOException; UNIXProcess(final byte[] prog, - final byte[] argBlock, int argc, - final byte[] envBlock, int envc, - final byte[] dir, - final boolean redirectErrorStream) + final byte[] argBlock, int argc, + final byte[] envBlock, int envc, + final byte[] dir, + final int[] std_fds, + final boolean redirectErrorStream) throws IOException { - stdin_fd = new FileDescriptor(); - stdout_fd = new FileDescriptor(); - stderr_fd = new FileDescriptor(); + pid = forkAndExec(prog, + argBlock, argc, + envBlock, envc, + dir, + std_fds, + redirectErrorStream); - pid = forkAndExec(prog, - argBlock, argc, - envBlock, envc, - dir, - redirectErrorStream, - stdin_fd, stdout_fd, stderr_fd); + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction<Void>() { public Void run() { + if (std_fds[0] == -1) + stdin_stream = new ProcessBuilder.NullOutputStream(); + else { + FileDescriptor stdin_fd = new FileDescriptor(); + fdAccess.set(stdin_fd, std_fds[0]); + stdin_stream = new BufferedOutputStream( + new FileOutputStream(stdin_fd)); + } - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Object run() { - stdin_stream - = new BufferedOutputStream(new FileOutputStream(stdin_fd)); - stdout_inner_stream = new DeferredCloseInputStream(stdout_fd); - stdout_stream = new BufferedInputStream(stdout_inner_stream); - stderr_stream = new DeferredCloseInputStream(stderr_fd); - return null; - } - }); + if (std_fds[1] == -1) + stdout_stream = new ProcessBuilder.NullInputStream(); + else { + FileDescriptor stdout_fd = new FileDescriptor(); + fdAccess.set(stdout_fd, std_fds[1]); + stdout_inner_stream = new DeferredCloseInputStream(stdout_fd); + stdout_stream = new BufferedInputStream(stdout_inner_stream); + } - /* - * For each subprocess forked a corresponding reaper thread - * is started. That thread is the only thread which waits - * for the subprocess to terminate and it doesn't hold any - * locks while doing so. This design allows waitFor() and - * exitStatus() to be safely executed in parallel (and they - * need no native code). - */ + if (std_fds[2] == -1) + stderr_stream = new ProcessBuilder.NullInputStream(); + else { + FileDescriptor stderr_fd = new FileDescriptor(); + fdAccess.set(stderr_fd, std_fds[2]); + stderr_stream = new DeferredCloseInputStream(stderr_fd); + } + + return null; }}); - java.security.AccessController.doPrivileged( - new java.security.PrivilegedAction() { - public Object run() { - Thread t = new Thread("process reaper") { - public void run() { - int res = waitForProcessExit(pid); - synchronized (UNIXProcess.this) { - hasExited = true; - exitcode = res; - UNIXProcess.this.notifyAll(); - } - } - }; - t.setDaemon(true); - t.start(); - return null; - } - }); + /* + * For each subprocess forked a corresponding reaper thread + * is started. That thread is the only thread which waits + * for the subprocess to terminate and it doesn't hold any + * locks while doing so. This design allows waitFor() and + * exitStatus() to be safely executed in parallel (and they + * need no native code). + */ + + java.security.AccessController.doPrivileged( + new java.security.PrivilegedAction<Void>() { public Void run() { + Thread t = new Thread("process reaper") { + public void run() { + int res = waitForProcessExit(pid); + synchronized (UNIXProcess.this) { + hasExited = true; + exitcode = res; + UNIXProcess.this.notifyAll(); + } + } + }; + t.setDaemon(true); + t.start(); + return null; }}); } public OutputStream getOutputStream() { - return stdin_stream; + return stdin_stream; } public InputStream getInputStream() { - return stdout_stream; + return stdout_stream; } public InputStream getErrorStream() { - return stderr_stream; + return stderr_stream; } public synchronized int waitFor() throws InterruptedException { while (!hasExited) { - wait(); - } - return exitcode; + wait(); + } + return exitcode; } public synchronized int exitValue() { - if (!hasExited) { - throw new IllegalThreadStateException("process hasn't exited"); - } - return exitcode; + if (!hasExited) { + throw new IllegalThreadStateException("process hasn't exited"); + } + return exitcode; } private static native void destroyProcess(int pid); public synchronized void destroy() { - // There is a risk that pid will be recycled, causing us to - // kill the wrong process! So we only terminate processes - // that appear to still be running. Even with this check, - // there is an unavoidable race condition here, but the window - // is very small, and OSes try hard to not recycle pids too - // soon, so this is quite safe. - if (!hasExited) - destroyProcess(pid); - try { + // There is a risk that pid will be recycled, causing us to + // kill the wrong process! So we only terminate processes + // that appear to still be running. Even with this check, + // there is an unavoidable race condition here, but the window + // is very small, and OSes try hard to not recycle pids too + // soon, so this is quite safe. + if (!hasExited) + destroyProcess(pid); + try { stdin_stream.close(); - stdout_inner_stream.closeDeferred(stdout_stream); - stderr_stream.closeDeferred(stderr_stream); + if (stdout_inner_stream != null) + stdout_inner_stream.closeDeferred(stdout_stream); + if (stderr_stream instanceof DeferredCloseInputStream) + ((DeferredCloseInputStream) stderr_stream) + .closeDeferred(stderr_stream); } catch (IOException e) { // ignore } @@ -172,99 +198,99 @@ // (EOF) as they did before. // private static class DeferredCloseInputStream - extends FileInputStream + extends FileInputStream { - private DeferredCloseInputStream(FileDescriptor fd) { - super(fd); - } + private DeferredCloseInputStream(FileDescriptor fd) { + super(fd); + } - private Object lock = new Object(); // For the following fields - private boolean closePending = false; - private int useCount = 0; - private InputStream streamToClose; + private Object lock = new Object(); // For the following fields + private boolean closePending = false; + private int useCount = 0; + private InputStream streamToClose; - private void raise() { - synchronized (lock) { - useCount++; - } - } + private void raise() { + synchronized (lock) { + useCount++; + } + } - private void lower() throws IOException { - synchronized (lock) { - useCount--; - if (useCount == 0 && closePending) { - streamToClose.close(); - } - } - } + private void lower() throws IOException { + synchronized (lock) { + useCount--; + if (useCount == 0 && closePending) { + streamToClose.close(); + } + } + } - // stc is the actual stream to be closed; it might be this object, or - // it might be an upstream object for which this object is downstream. - // - private void closeDeferred(InputStream stc) throws IOException { - synchronized (lock) { - if (useCount == 0) { - stc.close(); - } else { - closePending = true; - streamToClose = stc; - } - } - } + // stc is the actual stream to be closed; it might be this object, or + // it might be an upstream object for which this object is downstream. + // + private void closeDeferred(InputStream stc) throws IOException { + synchronized (lock) { + if (useCount == 0) { + stc.close(); + } else { + closePending = true; + streamToClose = stc; + } + } + } - public void close() throws IOException { - synchronized (lock) { - useCount = 0; - closePending = false; - } - super.close(); - } + public void close() throws IOException { + synchronized (lock) { + useCount = 0; + closePending = false; + } + super.close(); + } - public int read() throws IOException { - raise(); - try { - return super.read(); - } finally { - lower(); - } - } + public int read() throws IOException { + raise(); + try { + return super.read(); + } finally { + lower(); + } + } - public int read(byte[] b) throws IOException { - raise(); - try { - return super.read(b); - } finally { - lower(); - } - } + public int read(byte[] b) throws IOException { + raise(); + try { + return super.read(b); + } finally { + lower(); + } + } - public int read(byte[] b, int off, int len) throws IOException { - raise(); - try { - return super.read(b, off, len); - } finally { - lower(); - } - } + public int read(byte[] b, int off, int len) throws IOException { + raise(); + try { + return super.read(b, off, len); + } finally { + lower(); + } + } - public long skip(long n) throws IOException { - raise(); - try { - return super.skip(n); - } finally { - lower(); - } - } + public long skip(long n) throws IOException { + raise(); + try { + return super.skip(n); + } finally { + lower(); + } + } - public int available() throws IOException { - raise(); - try { - return super.available(); - } finally { - lower(); - } - } + public int available() throws IOException { + raise(); + try { + return super.available(); + } finally { + lower(); + } + } } @@ -272,6 +298,6 @@ private static native void initIDs(); static { - initIDs(); + initIDs(); } }
--- a/src/solaris/native/java/io/FileOutputStream_md.c Mon Mar 10 23:13:31 2008 +0100 +++ b/src/solaris/native/java/io/FileOutputStream_md.c Mon Mar 10 23:31:50 2008 +0100 @@ -53,13 +53,10 @@ */ JNIEXPORT void JNICALL -Java_java_io_FileOutputStream_open(JNIEnv *env, jobject this, jstring path) { - fileOpen(env, this, path, fos_fd, O_WRONLY | O_CREAT | O_TRUNC); -} - -JNIEXPORT void JNICALL -Java_java_io_FileOutputStream_openAppend(JNIEnv *env, jobject this, jstring path) { - fileOpen(env, this, path, fos_fd, O_WRONLY | O_CREAT | O_APPEND); +Java_java_io_FileOutputStream_open(JNIEnv *env, jobject this, + jstring path, jboolean append) { + fileOpen(env, this, path, fos_fd, + O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC)); } JNIEXPORT void JNICALL
--- a/src/solaris/native/java/lang/UNIXProcess_md.c Mon Mar 10 23:13:31 2008 +0100 +++ b/src/solaris/native/java/lang/UNIXProcess_md.c Mon Mar 10 23:31:50 2008 +0100 @@ -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 @@ -479,6 +479,37 @@ close(fd); } +/* + * Reads nbyte bytes from file descriptor fd into buf, + * The read operation is retried in case of EINTR or partial reads. + * + * Returns number of bytes read (normally nbyte, but may be less in + * case of EOF). In case of read errors, returns -1 and sets errno. + */ +static ssize_t +readFully(int fd, void *buf, size_t nbyte) +{ + ssize_t remaining = nbyte; + for (;;) { + ssize_t n = read(fd, buf, remaining); + if (n == 0) { + return nbyte - remaining; + } else if (n > 0) { + remaining -= n; + if (remaining <= 0) + return nbyte; + /* We were interrupted in the middle of reading the bytes. + * Unlikely, but possible. */ + buf = (void *) (((char *)buf) + n); + } else if (errno == EINTR) { + /* Strange signals like SIGJVM1 are possible at any time. + * See http://www.dreamsongs.com/WorseIsBetter.html */ + } else { + return -1; + } + } +} + #ifndef __solaris__ #undef fork1 #define fork1() fork() @@ -491,10 +522,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 +534,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 +557,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 +578,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 */ @@ -600,15 +637,21 @@ /* parent process */ close(fail[1]); fail[1] = -1; /* See: WhyCantJohnnyExec */ - if (read(fail[0], &errnum, sizeof(errnum)) != 0) { + + switch (readFully(fail[0], &errnum, sizeof(errnum))) { + case 0: break; /* Exec succeeded */ + case sizeof(errnum): waitpid(resultPid, NULL, 0); throwIOException(env, errnum, "Exec failed"); goto Catch; + default: + throwIOException(env, errno, "Read failed"); + 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 +671,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 23:13:31 2008 +0100 +++ b/src/windows/classes/java/io/FileDescriptor.java Mon Mar 10 23:31:50 2008 +0100 @@ -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 23:13:31 2008 +0100 +++ b/src/windows/classes/java/lang/ProcessImpl.java Mon Mar 10 23:31:50 2008 +0100 @@ -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/io/FileOutputStream_md.c Mon Mar 10 23:13:31 2008 +0100 +++ b/src/windows/native/java/io/FileOutputStream_md.c Mon Mar 10 23:31:50 2008 +0100 @@ -39,8 +39,6 @@ jfieldID fos_fd; /* id for jobject 'fd' in java.io.FileOutputStream */ -jfieldID fos_append; - /************************************************************** * static methods to store field ID's in initializers */ @@ -49,7 +47,6 @@ Java_java_io_FileOutputStream_initIDs(JNIEnv *env, jclass fosClass) { fos_fd = (*env)->GetFieldID(env, fosClass, "fd", "Ljava/io/FileDescriptor;"); - fos_append = (*env)->GetFieldID(env, fosClass, "append", "Z"); } /************************************************************** @@ -57,45 +54,20 @@ */ JNIEXPORT void JNICALL -Java_java_io_FileOutputStream_open(JNIEnv *env, jobject this, jstring path) { - fileOpen(env, this, path, fos_fd, O_WRONLY | O_CREAT | O_TRUNC); -} - -JNIEXPORT void JNICALL -Java_java_io_FileOutputStream_openAppend(JNIEnv *env, jobject this, jstring path) { - fileOpen(env, this, path, fos_fd, O_WRONLY | O_CREAT | O_APPEND); +Java_java_io_FileOutputStream_open(JNIEnv *env, jobject this, + jstring path, jboolean append) { + fileOpen(env, this, path, fos_fd, + O_WRONLY | O_CREAT | (append ? O_APPEND : O_TRUNC)); } JNIEXPORT void JNICALL Java_java_io_FileOutputStream_write(JNIEnv *env, jobject this, jint byte) { - jboolean append = (*env)->GetBooleanField(env, this, fos_append); - FD fd = GET_FD(this, fos_fd); - if (fd == -1) { - JNU_ThrowIOException(env, "Stream Closed"); - return; - } - if (append == JNI_TRUE) { - if (IO_Lseek(fd, 0L, SEEK_END) == -1) { - JNU_ThrowIOExceptionWithLastError(env, "Append failed"); - } - } writeSingle(env, this, byte, fos_fd); } JNIEXPORT void JNICALL Java_java_io_FileOutputStream_writeBytes(JNIEnv *env, jobject this, jbyteArray bytes, jint off, jint len) { - jboolean append = (*env)->GetBooleanField(env, this, fos_append); - FD fd = GET_FD(this, fos_fd); - if (fd == -1) { - JNU_ThrowIOException(env, "Stream Closed"); - return; - } - if (append == JNI_TRUE) { - if (IO_Lseek(fd, 0L, SEEK_END) == -1) { - JNU_ThrowIOExceptionWithLastError(env, "Append failed"); - } - } writeBytes(env, this, bytes, off, len, fos_fd); }
--- a/src/windows/native/java/io/io_util_md.c Mon Mar 10 23:13:31 2008 +0100 +++ b/src/windows/native/java/io/io_util_md.c Mon Mar 10 23:31:50 2008 +0100 @@ -42,7 +42,7 @@ extern jboolean onNT = JNI_FALSE; -static int MAX_INPUT_EVENTS = 2000; +static DWORD MAX_INPUT_EVENTS = 2000; void initializeWindowsVersion() { @@ -190,9 +190,16 @@ jlong winFileHandleOpen(JNIEnv *env, jstring path, int flags) { + /* To implement O_APPEND, we use the strategy from + http://msdn2.microsoft.com/en-us/library/aa363858.aspx + "You can get atomic append by opening a file with + FILE_APPEND_DATA access and _without_ FILE_WRITE_DATA access. + If you do this then all writes will ignore the current file + pointer and be done at the end-of file." */ const DWORD access = - (flags & O_RDWR) ? (GENERIC_WRITE | GENERIC_READ) : + (flags & O_APPEND) ? (FILE_GENERIC_WRITE & ~FILE_WRITE_DATA) : (flags & O_WRONLY) ? GENERIC_WRITE : + (flags & O_RDWR) ? (GENERIC_READ | GENERIC_WRITE) : GENERIC_READ; const DWORD sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; @@ -444,24 +451,6 @@ return 0; } -int -handleFileSizeFD(jlong fd, jlong *size) -{ - DWORD sizeLow = 0; - DWORD sizeHigh = 0; - HANDLE h = (HANDLE)fd; - if (h == INVALID_HANDLE_VALUE) { - return -1; - } - sizeLow = GetFileSize(h, &sizeHigh); - if (sizeLow == ((DWORD)-1)) { - if (GetLastError() != ERROR_SUCCESS) { - return -1; - } - } - return (((jlong)sizeHigh) << 32) | sizeLow; -} - JNIEXPORT size_t handleRead(jlong fd, void *buf, jint len) @@ -513,7 +502,7 @@ FD fd = GET_FD(this, fid); HANDLE h = (HANDLE)fd; - if (fd == INVALID_HANDLE_VALUE) { + if (h == INVALID_HANDLE_VALUE) { return 0; }
--- a/src/windows/native/java/io/io_util_md.h Mon Mar 10 23:13:31 2008 +0100 +++ b/src/windows/native/java/io/io_util_md.h Mon Mar 10 23:31:50 2008 +0100 @@ -38,7 +38,6 @@ int handleAvailable(jlong fd, jlong *pbytes); JNIEXPORT int handleSync(jlong fd); int handleSetLength(jlong fd, jlong length); -int handleFileSizeFD(jlong fd, jlong *size); JNIEXPORT size_t handleRead(jlong fd, void *buf, jint len); JNIEXPORT size_t handleWrite(jlong fd, const void *buf, jint len); jint handleClose(JNIEnv *env, jobject this, jfieldID fid);
--- a/src/windows/native/java/lang/ProcessImpl_md.c Mon Mar 10 23:13:31 2008 +0100 +++ b/src/windows/native/java/lang/ProcessImpl_md.c Mon Mar 10 23:31:50 2008 +0100 @@ -33,7 +33,12 @@ #include <windows.h> #include <io.h> -#define PIPE_SIZE 4096 +/* We try to make sure that we can read and write 4095 bytes (the + * fixed limit on Linux) to the pipe on all operating systems without + * deadlock. Windows 2000 inexplicably appears to need an extra 24 + * bytes of slop to avoid deadlock. + */ +#define PIPE_SIZE (4096+24) char * extractExecutablePath(JNIEnv *env, char *source) @@ -120,7 +125,7 @@ static void closeSafely(HANDLE handle) { - if (handle) + if (handle != INVALID_HANDLE_VALUE) CloseHandle(handle); } @@ -129,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; @@ -156,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; @@ -184,19 +177,62 @@ if (penvBlock == NULL) goto Catch; } + assert(stdHandles != NULL); + handles = (*env)->GetLongArrayElements(env, stdHandles, NULL); + if (handles == NULL) goto Catch; + memset(&si, 0, sizeof(si)); si.cb = sizeof(si); si.dwFlags = STARTF_USESTDHANDLES; - si.hStdInput = inRead; - si.hStdOutput = outWrite; - si.hStdError = redirectErrorStream ? outWrite : errWrite; + + sa.nLength = sizeof(sa); + sa.lpSecurityDescriptor = 0; + sa.bInheritHandle = TRUE; + + if (handles[0] != (jlong) -1) { + si.hStdInput = (HANDLE) handles[0]; + handles[0] = (jlong) -1; + } else { + if (! CreatePipe(&inRead, &inWrite, &sa, PIPE_SIZE)) { + win32Error(env, "CreatePipe"); + goto Catch; + } + si.hStdInput = inRead; + SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE); + handles[0] = (jlong) inWrite; + } + SetHandleInformation(si.hStdInput, HANDLE_FLAG_INHERIT, TRUE); - SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE); - SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE); - SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE); + if (handles[1] != (jlong) -1) { + si.hStdOutput = (HANDLE) handles[1]; + handles[1] = (jlong) -1; + } else { + if (! CreatePipe(&outRead, &outWrite, &sa, PIPE_SIZE)) { + win32Error(env, "CreatePipe"); + goto Catch; + } + si.hStdOutput = outWrite; + SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE); + handles[1] = (jlong) outRead; + } + SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT, TRUE); - if (redirectErrorStream) - SetHandleInformation(errWrite, HANDLE_FLAG_INHERIT, FALSE); + if (redirectErrorStream) { + si.hStdError = si.hStdOutput; + handles[2] = (jlong) -1; + } else if (handles[2] != (jlong) -1) { + si.hStdError = (HANDLE) handles[2]; + handles[2] = (jlong) -1; + } else { + if (! CreatePipe(&errRead, &errWrite, &sa, PIPE_SIZE)) { + win32Error(env, "CreatePipe"); + goto Catch; + } + si.hStdError = errWrite; + SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE); + handles[2] = (jlong) errRead; + } + SetHandleInformation(si.hStdError, HANDLE_FLAG_INHERIT, TRUE); if (onNT) processFlag = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT; @@ -232,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 */ @@ -252,6 +285,9 @@ else JNU_ReleaseStringPlatformChars(env, dir, (char *) penvBlock); } + if (handles != NULL) + (*env)->ReleaseLongArrayElements(env, stdHandles, handles, 0); + return ret; Catch:
--- a/src/windows/native/java/lang/java_props_md.c Mon Mar 10 23:13:31 2008 +0100 +++ b/src/windows/native/java/lang/java_props_md.c Mon Mar 10 23:31:50 2008 +0100 @@ -673,13 +673,13 @@ /* OS properties */ { char buf[100]; - OSVERSIONINFO ver; + OSVERSIONINFOEX ver; ver.dwOSVersionInfoSize = sizeof(ver); - GetVersionEx(&ver); + GetVersionEx((OSVERSIONINFO *) &ver); /* * From msdn page on OSVERSIONINFOEX, current as of this - * writing decoding of dwMajorVersion and dwMinorVersion. + * writing, decoding of dwMajorVersion and dwMinorVersion. * * Operating system dwMajorVersion dwMinorVersion * ================== ============== ============== @@ -692,7 +692,7 @@ * Windows 2000 5 0 * Windows XP 5 1 * Windows Server 2003 family 5 2 - * Windows Vista 6 0 + * Windows Vista family 6 0 * * This mapping will presumably be augmented as new Windows * versions are released. @@ -724,7 +724,20 @@ default: sprops.os_name = "Windows NT (unknown)"; break; } } else if (ver.dwMajorVersion == 6) { - sprops.os_name = "Windows Vista"; + /* + * From MSDN OSVERSIONINFOEX documentation: + * + * "Because the version numbers for Windows Server 2008 + * and Windows Vista are identical, you must also test + * whether the wProductType member is VER_NT_WORKSTATION. + * If wProductType is VER_NT_WORKSTATION, the operating + * system is Windows Vista; otherwise, it is Windows + * Server 2008." + */ + if (ver.wProductType == VER_NT_WORKSTATION) + sprops.os_name = "Windows Vista"; + else + sprops.os_name = "Windows Server 2008"; } else { sprops.os_name = "Windows NT (unknown)"; }
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/test/java/io/FileOutputStream/AtomicAppend.java Mon Mar 10 23:31:50 2008 +0100 @@ -0,0 +1,81 @@ +/* + * Copyright 2007 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 + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, + * CA 95054 USA or visit www.sun.com if you need additional information or + * have any questions. + */ + +/* + * @test + * @bug 6631352 + * @summary Check that appends are atomic + */ + +import java.io.File; +import java.io.FileOutputStream; +import java.util.concurrent.Executors; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; + +public class AtomicAppend { + // Before the fix for + // 6631352: Implement atomic append mode using FILE_APPEND_DATA (win) + // this would fail intermittently on windows + void test(String[] args) throws Throwable { + final int nThreads = 10; + final int writes = 1000; + final File file = new File("foo"); + file.delete(); + try { + final ExecutorService es = Executors.newFixedThreadPool(nThreads); + for (int i = 0; i < nThreads; i++) + es.execute(new Runnable() { public void run() { + try { + FileOutputStream s = new FileOutputStream(file, true); + for (int j = 0; j < 1000; j++) { + s.write((int) 'x'); + s.flush(); + } + s.close(); + } catch (Throwable t) { unexpected(t); }}}); + es.shutdown(); + es.awaitTermination(10L, TimeUnit.MINUTES); + equal(file.length(), (long) (nThreads * writes)); + } finally { + file.delete(); + } + } + + //--------------------- Infrastructure --------------------------- + volatile int passed = 0, failed = 0; + void pass() {passed++;} + void fail() {failed++; Thread.dumpStack();} + void fail(String msg) {System.err.println(msg); fail();} + void unexpected(Throwable t) {failed++; t.printStackTrace();} + void check(boolean cond) {if (cond) pass(); else fail();} + void equal(Object x, Object y) { + if (x == null ? y == null : x.equals(y)) pass(); + else fail(x + " not equal to " + y);} + public static void main(String[] args) throws Throwable { + new AtomicAppend().instanceMain(args);} + void instanceMain(String[] args) throws Throwable { + try {test(args);} catch (Throwable t) {unexpected(t);} + System.out.printf("%nPassed = %d, failed = %d%n%n", passed, failed); + if (failed > 0) throw new AssertionError("Some tests failed");} +}
--- a/test/java/lang/ProcessBuilder/Basic.java Mon Mar 10 23:13:31 2008 +0100 +++ b/test/java/lang/ProcessBuilder/Basic.java Mon Mar 10 23:31:50 2008 +0100 @@ -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;