4960438: (process) Need IO redirection API for subprocesses
Reviewed-by: alanb, iris
This commit is contained in:
parent
504a24907d
commit
abde1241e1
@ -41,18 +41,24 @@ import java.io.*;
|
||||
* <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 @@ import java.io.*;
|
||||
* 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 @@ public abstract class Process {
|
||||
/**
|
||||
* 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();
|
||||
|
||||
|
@ -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 @@ package java.lang;
|
||||
|
||||
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 @@ import java.util.Map;
|
||||
/**
|
||||
* 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 @@ import java.util.Map;
|
||||
*
|
||||
* <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 @@ import java.util.Map;
|
||||
* 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 @@ public final class ProcessBuilder
|
||||
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 @@ public final class ProcessBuilder
|
||||
* 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 @@ public final class ProcessBuilder
|
||||
/**
|
||||
* 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 @@ public final class ProcessBuilder
|
||||
* 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 @@ public final class ProcessBuilder
|
||||
* 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 @@ public final class ProcessBuilder
|
||||
* <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 @@ public final class ProcessBuilder
|
||||
* <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 @@ public final class ProcessBuilder
|
||||
*
|
||||
* 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 @@ public final class ProcessBuilder
|
||||
*
|
||||
* 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 @@ public final class ProcessBuilder
|
||||
* <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 @@ public final class ProcessBuilder
|
||||
* subclass of {@link IOException}.
|
||||
*
|
||||
* <p>Subsequent modifications to this process builder will not
|
||||
* affect the returned {@link Process}.</p>
|
||||
* affect the returned {@link Process}.
|
||||
*
|
||||
* @return A new {@link Process} object for managing the subprocess
|
||||
* @return a new {@link Process} object for managing the subprocess
|
||||
*
|
||||
* @throws NullPointerException
|
||||
* If an element of the command list is null
|
||||
* @throws NullPointerException
|
||||
* if an element of the command list is null
|
||||
*
|
||||
* @throws IndexOutOfBoundsException
|
||||
* If the command is an empty list (has size <code>0</code>)
|
||||
* @throws IndexOutOfBoundsException
|
||||
* if the command is an empty list (has size {@code 0})
|
||||
*
|
||||
* @throws SecurityException
|
||||
* If a security manager exists and its
|
||||
* {@link SecurityManager#checkExec checkExec}
|
||||
* method doesn't allow creation of the subprocess
|
||||
* @throws SecurityException
|
||||
* if a security manager exists and
|
||||
* <ul>
|
||||
*
|
||||
* @throws IOException
|
||||
* If an I/O error occurs
|
||||
* <li>its
|
||||
* {@link SecurityManager#checkExec checkExec}
|
||||
* method doesn't allow creation of the subprocess, or
|
||||
*
|
||||
* @see Runtime#exec(String[], String[], java.io.File)
|
||||
* @see SecurityManager#checkExec(String)
|
||||
* <li>the standard input to the subprocess was
|
||||
* {@linkplain #redirectInput redirected from a file}
|
||||
* and the security manager's
|
||||
* {@link SecurityManager#checkRead checkRead} method
|
||||
* denies read access to the file, or
|
||||
*
|
||||
* <li>the standard output or standard error of the
|
||||
* subprocess was
|
||||
* {@linkplain #redirectOutput redirected to a file}
|
||||
* and the security manager's
|
||||
* {@link SecurityManager#checkWrite checkWrite} method
|
||||
* denies write access to the file
|
||||
*
|
||||
* </ul>
|
||||
*
|
||||
* @throws IOException if an I/O error occurs
|
||||
*
|
||||
* @see Runtime#exec(String[], String[], java.io.File)
|
||||
*/
|
||||
public Process start() throws IOException {
|
||||
// Must convert to array first -- a malicious user-supplied
|
||||
@ -467,6 +1010,7 @@ public final class ProcessBuilder
|
||||
return ProcessImpl.start(cmdarray,
|
||||
environment,
|
||||
dir,
|
||||
redirects,
|
||||
redirectErrorStream);
|
||||
} catch (IOException e) {
|
||||
// It's much easier for us to create a high-quality error
|
||||
|
@ -33,4 +33,8 @@ import java.io.FileDescriptor;
|
||||
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);
|
||||
}
|
||||
|
@ -152,11 +152,19 @@ public final class FileDescriptor {
|
||||
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();
|
||||
|
@ -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 @@ import java.lang.Process;
|
||||
* @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 @@ final class ProcessImpl {
|
||||
static Process start(String[] cmdarray,
|
||||
java.util.Map<String,String> environment,
|
||||
String dir,
|
||||
ProcessBuilder.Redirect[] redirects,
|
||||
boolean redirectErrorStream)
|
||||
throws IOException
|
||||
{
|
||||
@ -78,11 +85,61 @@ final class ProcessImpl {
|
||||
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(); }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 @@ import java.io.*;
|
||||
*/
|
||||
|
||||
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 @@ final class UNIXProcess extends Process {
|
||||
/* 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 @@ final class UNIXProcess extends Process {
|
||||
}
|
||||
|
||||
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 @@ final class UNIXProcess extends Process {
|
||||
}
|
||||
|
||||
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 @@ final class UNIXProcess extends Process {
|
||||
private static native void initIDs();
|
||||
|
||||
static {
|
||||
initIDs();
|
||||
initIDs();
|
||||
}
|
||||
}
|
||||
|
@ -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 @@ import java.io.*;
|
||||
*/
|
||||
|
||||
final class UNIXProcess extends Process {
|
||||
private FileDescriptor stdin_fd;
|
||||
private FileDescriptor stdout_fd;
|
||||
private FileDescriptor stderr_fd;
|
||||
private int pid;
|
||||
private static final sun.misc.JavaIOFileDescriptorAccess fdAccess
|
||||
= sun.misc.SharedSecrets.getJavaIOFileDescriptorAccess();
|
||||
|
||||
private final int pid;
|
||||
private int exitcode;
|
||||
private boolean hasExited;
|
||||
|
||||
private OutputStream stdin_stream;
|
||||
private BufferedInputStream stdout_stream;
|
||||
private InputStream stdout_stream;
|
||||
private DeferredCloseInputStream stdout_inner_stream;
|
||||
private DeferredCloseInputStream stderr_stream;
|
||||
private InputStream stderr_stream;
|
||||
|
||||
/* this is for the reaping thread */
|
||||
private native int waitForProcessExit(int pid);
|
||||
|
||||
/**
|
||||
* Create a process using fork(2) and exec(2).
|
||||
*
|
||||
* @param std_fds array of file descriptors. Indexes 0, 1, and
|
||||
* 2 correspond to standard input, standard output and
|
||||
* standard error, respectively. On input, a value of -1
|
||||
* means to create a pipe to connect child and parent
|
||||
* processes. On output, a value which is not -1 is the
|
||||
* parent pipe fd corresponding to the pipe which has
|
||||
* been created. An element of this array is -1 on input
|
||||
* if and only if it is <em>not</em> -1 on output.
|
||||
* @return the pid of the subprocess
|
||||
*/
|
||||
private native int forkAndExec(byte[] prog,
|
||||
byte[] argBlock, int argc,
|
||||
byte[] envBlock, int envc,
|
||||
byte[] dir,
|
||||
boolean redirectErrorStream,
|
||||
FileDescriptor stdin_fd,
|
||||
FileDescriptor stdout_fd,
|
||||
FileDescriptor stderr_fd)
|
||||
throws IOException;
|
||||
byte[] argBlock, int argc,
|
||||
byte[] envBlock, int envc,
|
||||
byte[] dir,
|
||||
int[] std_fds,
|
||||
boolean redirectErrorStream)
|
||||
throws IOException;
|
||||
|
||||
UNIXProcess(final byte[] prog,
|
||||
final byte[] argBlock, int argc,
|
||||
final byte[] envBlock, int envc,
|
||||
final byte[] dir,
|
||||
final boolean redirectErrorStream)
|
||||
final byte[] argBlock, int argc,
|
||||
final byte[] envBlock, int envc,
|
||||
final byte[] dir,
|
||||
final int[] std_fds,
|
||||
final boolean redirectErrorStream)
|
||||
throws IOException {
|
||||
stdin_fd = new FileDescriptor();
|
||||
stdout_fd = new FileDescriptor();
|
||||
stderr_fd = new FileDescriptor();
|
||||
pid = forkAndExec(prog,
|
||||
argBlock, argc,
|
||||
envBlock, envc,
|
||||
dir,
|
||||
std_fds,
|
||||
redirectErrorStream);
|
||||
|
||||
pid = forkAndExec(prog,
|
||||
argBlock, argc,
|
||||
envBlock, envc,
|
||||
dir,
|
||||
redirectErrorStream,
|
||||
stdin_fd, stdout_fd, stderr_fd);
|
||||
java.security.AccessController.doPrivileged(
|
||||
new java.security.PrivilegedAction<Void>() { public Void run() {
|
||||
if (std_fds[0] == -1)
|
||||
stdin_stream = new ProcessBuilder.NullOutputStream();
|
||||
else {
|
||||
FileDescriptor stdin_fd = new FileDescriptor();
|
||||
fdAccess.set(stdin_fd, std_fds[0]);
|
||||
stdin_stream = new BufferedOutputStream(
|
||||
new FileOutputStream(stdin_fd));
|
||||
}
|
||||
|
||||
java.security.AccessController.doPrivileged(
|
||||
new java.security.PrivilegedAction() {
|
||||
public Object run() {
|
||||
stdin_stream
|
||||
= new BufferedOutputStream(new FileOutputStream(stdin_fd));
|
||||
stdout_inner_stream = new DeferredCloseInputStream(stdout_fd);
|
||||
stdout_stream = new BufferedInputStream(stdout_inner_stream);
|
||||
stderr_stream = new DeferredCloseInputStream(stderr_fd);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
if (std_fds[1] == -1)
|
||||
stdout_stream = new ProcessBuilder.NullInputStream();
|
||||
else {
|
||||
FileDescriptor stdout_fd = new FileDescriptor();
|
||||
fdAccess.set(stdout_fd, std_fds[1]);
|
||||
stdout_inner_stream = new DeferredCloseInputStream(stdout_fd);
|
||||
stdout_stream = new BufferedInputStream(stdout_inner_stream);
|
||||
}
|
||||
|
||||
/*
|
||||
* For each subprocess forked a corresponding reaper thread
|
||||
* is started. That thread is the only thread which waits
|
||||
* for the subprocess to terminate and it doesn't hold any
|
||||
* locks while doing so. This design allows waitFor() and
|
||||
* exitStatus() to be safely executed in parallel (and they
|
||||
* need no native code).
|
||||
*/
|
||||
if (std_fds[2] == -1)
|
||||
stderr_stream = new ProcessBuilder.NullInputStream();
|
||||
else {
|
||||
FileDescriptor stderr_fd = new FileDescriptor();
|
||||
fdAccess.set(stderr_fd, std_fds[2]);
|
||||
stderr_stream = new DeferredCloseInputStream(stderr_fd);
|
||||
}
|
||||
|
||||
java.security.AccessController.doPrivileged(
|
||||
new java.security.PrivilegedAction() {
|
||||
public Object run() {
|
||||
Thread t = new Thread("process reaper") {
|
||||
public void run() {
|
||||
int res = waitForProcessExit(pid);
|
||||
synchronized (UNIXProcess.this) {
|
||||
hasExited = true;
|
||||
exitcode = res;
|
||||
UNIXProcess.this.notifyAll();
|
||||
}
|
||||
}
|
||||
};
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
return null;
|
||||
}
|
||||
});
|
||||
return null; }});
|
||||
|
||||
/*
|
||||
* For each subprocess forked a corresponding reaper thread
|
||||
* is started. That thread is the only thread which waits
|
||||
* for the subprocess to terminate and it doesn't hold any
|
||||
* locks while doing so. This design allows waitFor() and
|
||||
* exitStatus() to be safely executed in parallel (and they
|
||||
* need no native code).
|
||||
*/
|
||||
|
||||
java.security.AccessController.doPrivileged(
|
||||
new java.security.PrivilegedAction<Void>() { public Void run() {
|
||||
Thread t = new Thread("process reaper") {
|
||||
public void run() {
|
||||
int res = waitForProcessExit(pid);
|
||||
synchronized (UNIXProcess.this) {
|
||||
hasExited = true;
|
||||
exitcode = res;
|
||||
UNIXProcess.this.notifyAll();
|
||||
}
|
||||
}
|
||||
};
|
||||
t.setDaemon(true);
|
||||
t.start();
|
||||
return null; }});
|
||||
}
|
||||
|
||||
public OutputStream getOutputStream() {
|
||||
return stdin_stream;
|
||||
return stdin_stream;
|
||||
}
|
||||
|
||||
public InputStream getInputStream() {
|
||||
return stdout_stream;
|
||||
return stdout_stream;
|
||||
}
|
||||
|
||||
public InputStream getErrorStream() {
|
||||
return stderr_stream;
|
||||
return stderr_stream;
|
||||
}
|
||||
|
||||
public synchronized int waitFor() throws InterruptedException {
|
||||
while (!hasExited) {
|
||||
wait();
|
||||
}
|
||||
return exitcode;
|
||||
wait();
|
||||
}
|
||||
return exitcode;
|
||||
}
|
||||
|
||||
public synchronized int exitValue() {
|
||||
if (!hasExited) {
|
||||
throw new IllegalThreadStateException("process hasn't exited");
|
||||
}
|
||||
return exitcode;
|
||||
if (!hasExited) {
|
||||
throw new IllegalThreadStateException("process hasn't exited");
|
||||
}
|
||||
return exitcode;
|
||||
}
|
||||
|
||||
private static native void destroyProcess(int pid);
|
||||
public synchronized void destroy() {
|
||||
// There is a risk that pid will be recycled, causing us to
|
||||
// kill the wrong process! So we only terminate processes
|
||||
// that appear to still be running. Even with this check,
|
||||
// there is an unavoidable race condition here, but the window
|
||||
// is very small, and OSes try hard to not recycle pids too
|
||||
// soon, so this is quite safe.
|
||||
if (!hasExited)
|
||||
destroyProcess(pid);
|
||||
try {
|
||||
// There is a risk that pid will be recycled, causing us to
|
||||
// kill the wrong process! So we only terminate processes
|
||||
// that appear to still be running. Even with this check,
|
||||
// there is an unavoidable race condition here, but the window
|
||||
// is very small, and OSes try hard to not recycle pids too
|
||||
// soon, so this is quite safe.
|
||||
if (!hasExited)
|
||||
destroyProcess(pid);
|
||||
try {
|
||||
stdin_stream.close();
|
||||
stdout_inner_stream.closeDeferred(stdout_stream);
|
||||
stderr_stream.closeDeferred(stderr_stream);
|
||||
if (stdout_inner_stream != null)
|
||||
stdout_inner_stream.closeDeferred(stdout_stream);
|
||||
if (stderr_stream instanceof DeferredCloseInputStream)
|
||||
((DeferredCloseInputStream) stderr_stream)
|
||||
.closeDeferred(stderr_stream);
|
||||
} catch (IOException e) {
|
||||
// ignore
|
||||
}
|
||||
@ -172,99 +198,99 @@ final class UNIXProcess extends Process {
|
||||
// (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 @@ final class UNIXProcess extends Process {
|
||||
private static native void initIDs();
|
||||
|
||||
static {
|
||||
initIDs();
|
||||
initIDs();
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 1995-2006 Sun Microsystems, Inc. All Rights Reserved.
|
||||
* Copyright 1995-2008 Sun Microsystems, Inc. All Rights Reserved.
|
||||
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
||||
*
|
||||
* This code is free software; you can redistribute it and/or modify it
|
||||
@ -491,10 +491,8 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
|
||||
jbyteArray argBlock, jint argc,
|
||||
jbyteArray envBlock, jint envc,
|
||||
jbyteArray dir,
|
||||
jboolean redirectErrorStream,
|
||||
jobject stdin_fd,
|
||||
jobject stdout_fd,
|
||||
jobject stderr_fd)
|
||||
jintArray std_fds,
|
||||
jboolean redirectErrorStream)
|
||||
{
|
||||
int errnum;
|
||||
int resultPid = -1;
|
||||
@ -505,6 +503,7 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
|
||||
const char *pargBlock = getBytes(env, argBlock);
|
||||
const char *penvBlock = getBytes(env, envBlock);
|
||||
const char *pdir = getBytes(env, dir);
|
||||
jint *fds = NULL;
|
||||
|
||||
in[0] = in[1] = out[0] = out[1] = err[0] = err[1] = fail[0] = fail[1] = -1;
|
||||
|
||||
@ -527,9 +526,13 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
|
||||
initVectorFromBlock(envv, penvBlock, envc);
|
||||
}
|
||||
|
||||
if ((pipe(in) < 0) ||
|
||||
(pipe(out) < 0) ||
|
||||
(pipe(err) < 0) ||
|
||||
assert(std_fds != NULL);
|
||||
fds = (*env)->GetIntArrayElements(env, std_fds, NULL);
|
||||
if (fds == NULL) goto Catch;
|
||||
|
||||
if ((fds[0] == -1 && pipe(in) < 0) ||
|
||||
(fds[1] == -1 && pipe(out) < 0) ||
|
||||
(fds[2] == -1 && pipe(err) < 0) ||
|
||||
(pipe(fail) < 0)) {
|
||||
throwIOException(env, errno, "Bad file descriptor");
|
||||
goto Catch;
|
||||
@ -544,23 +547,26 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
|
||||
if (resultPid == 0) {
|
||||
/* Child process */
|
||||
|
||||
/* Close the parent sides of the pipe.
|
||||
Give the child sides of the pipes the right fileno's.
|
||||
/* Close the parent sides of the pipes.
|
||||
Closing pipe fds here is redundant, since closeDescriptors()
|
||||
would do it anyways, but a little paranoia is a good thing. */
|
||||
closeSafely(in[1]);
|
||||
closeSafely(out[0]);
|
||||
closeSafely(err[0]);
|
||||
closeSafely(fail[0]);
|
||||
|
||||
/* Give the child sides of the pipes the right fileno's. */
|
||||
/* Note: it is possible for in[0] == 0 */
|
||||
close(in[1]);
|
||||
moveDescriptor(in[0], STDIN_FILENO);
|
||||
close(out[0]);
|
||||
moveDescriptor(out[1], STDOUT_FILENO);
|
||||
close(err[0]);
|
||||
moveDescriptor(in[0] != -1 ? in[0] : fds[0], STDIN_FILENO);
|
||||
moveDescriptor(out[1]!= -1 ? out[1] : fds[1], STDOUT_FILENO);
|
||||
|
||||
if (redirectErrorStream) {
|
||||
close(err[1]);
|
||||
closeSafely(err[1]);
|
||||
dup2(STDOUT_FILENO, STDERR_FILENO);
|
||||
} else {
|
||||
moveDescriptor(err[1], STDERR_FILENO);
|
||||
moveDescriptor(err[1] != -1 ? err[1] : fds[2], STDERR_FILENO);
|
||||
}
|
||||
close(fail[0]);
|
||||
|
||||
moveDescriptor(fail[1], FAIL_FILENO);
|
||||
|
||||
/* close everything */
|
||||
@ -606,9 +612,9 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
|
||||
goto Catch;
|
||||
}
|
||||
|
||||
(*env)->SetIntField(env, stdin_fd, IO_fd_fdID, in [1]);
|
||||
(*env)->SetIntField(env, stdout_fd, IO_fd_fdID, out[0]);
|
||||
(*env)->SetIntField(env, stderr_fd, IO_fd_fdID, err[0]);
|
||||
fds[0] = (in [1] != -1) ? in [1] : -1;
|
||||
fds[1] = (out[0] != -1) ? out[0] : -1;
|
||||
fds[2] = (err[0] != -1) ? err[0] : -1;
|
||||
|
||||
Finally:
|
||||
/* Always clean up the child's side of the pipes */
|
||||
@ -628,6 +634,9 @@ Java_java_lang_UNIXProcess_forkAndExec(JNIEnv *env,
|
||||
releaseBytes(env, envBlock, penvBlock);
|
||||
releaseBytes(env, dir, pdir);
|
||||
|
||||
if (fds != NULL)
|
||||
(*env)->ReleaseIntArrayElements(env, std_fds, fds, 0);
|
||||
|
||||
return resultPid;
|
||||
|
||||
Catch:
|
||||
|
@ -29,17 +29,14 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* 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 final class FileDescriptor {
|
||||
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 @@ public final class FileDescriptor {
|
||||
/**
|
||||
* 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 @@ public final class FileDescriptor {
|
||||
/**
|
||||
* 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 @@ public final class FileDescriptor {
|
||||
/**
|
||||
* 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 @@ public final class FileDescriptor {
|
||||
/**
|
||||
* 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));
|
||||
|
@ -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 @@ import java.io.*;
|
||||
*/
|
||||
|
||||
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 @@ final class ProcessImpl extends Process {
|
||||
}
|
||||
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 @@ final class ProcessImpl extends Process {
|
||||
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);
|
||||
|
@ -125,7 +125,7 @@ win32Error(JNIEnv *env, const char *functionName)
|
||||
static void
|
||||
closeSafely(HANDLE handle)
|
||||
{
|
||||
if (handle)
|
||||
if (handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(handle);
|
||||
}
|
||||
|
||||
@ -134,23 +134,22 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
|
||||
jstring cmd,
|
||||
jstring envBlock,
|
||||
jstring dir,
|
||||
jboolean redirectErrorStream,
|
||||
jobject in_fd,
|
||||
jobject out_fd,
|
||||
jobject err_fd)
|
||||
jlongArray stdHandles,
|
||||
jboolean redirectErrorStream)
|
||||
{
|
||||
HANDLE inRead = 0;
|
||||
HANDLE inWrite = 0;
|
||||
HANDLE outRead = 0;
|
||||
HANDLE outWrite = 0;
|
||||
HANDLE errRead = 0;
|
||||
HANDLE errWrite = 0;
|
||||
HANDLE inRead = INVALID_HANDLE_VALUE;
|
||||
HANDLE inWrite = INVALID_HANDLE_VALUE;
|
||||
HANDLE outRead = INVALID_HANDLE_VALUE;
|
||||
HANDLE outWrite = INVALID_HANDLE_VALUE;
|
||||
HANDLE errRead = INVALID_HANDLE_VALUE;
|
||||
HANDLE errWrite = INVALID_HANDLE_VALUE;
|
||||
SECURITY_ATTRIBUTES sa;
|
||||
PROCESS_INFORMATION pi;
|
||||
STARTUPINFO si;
|
||||
LPTSTR pcmd = NULL;
|
||||
LPCTSTR pdir = NULL;
|
||||
LPVOID penvBlock = NULL;
|
||||
jlong *handles = NULL;
|
||||
jlong ret = 0;
|
||||
OSVERSIONINFO ver;
|
||||
jboolean onNT = JNI_FALSE;
|
||||
@ -161,17 +160,6 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
|
||||
if (ver.dwPlatformId == VER_PLATFORM_WIN32_NT)
|
||||
onNT = JNI_TRUE;
|
||||
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.lpSecurityDescriptor = 0;
|
||||
sa.bInheritHandle = TRUE;
|
||||
|
||||
if (!(CreatePipe(&inRead, &inWrite, &sa, PIPE_SIZE) &&
|
||||
CreatePipe(&outRead, &outWrite, &sa, PIPE_SIZE) &&
|
||||
CreatePipe(&errRead, &errWrite, &sa, PIPE_SIZE))) {
|
||||
win32Error(env, "CreatePipe");
|
||||
goto Catch;
|
||||
}
|
||||
|
||||
assert(cmd != NULL);
|
||||
pcmd = (LPTSTR) JNU_GetStringPlatformChars(env, cmd, NULL);
|
||||
if (pcmd == NULL) goto Catch;
|
||||
@ -189,19 +177,62 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
|
||||
if (penvBlock == NULL) goto Catch;
|
||||
}
|
||||
|
||||
assert(stdHandles != NULL);
|
||||
handles = (*env)->GetLongArrayElements(env, stdHandles, NULL);
|
||||
if (handles == NULL) goto Catch;
|
||||
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
si.dwFlags = STARTF_USESTDHANDLES;
|
||||
si.hStdInput = inRead;
|
||||
si.hStdOutput = outWrite;
|
||||
si.hStdError = redirectErrorStream ? outWrite : errWrite;
|
||||
|
||||
SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE);
|
||||
SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE);
|
||||
SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE);
|
||||
sa.nLength = sizeof(sa);
|
||||
sa.lpSecurityDescriptor = 0;
|
||||
sa.bInheritHandle = TRUE;
|
||||
|
||||
if (redirectErrorStream)
|
||||
SetHandleInformation(errWrite, HANDLE_FLAG_INHERIT, FALSE);
|
||||
if (handles[0] != (jlong) -1) {
|
||||
si.hStdInput = (HANDLE) handles[0];
|
||||
handles[0] = (jlong) -1;
|
||||
} else {
|
||||
if (! CreatePipe(&inRead, &inWrite, &sa, PIPE_SIZE)) {
|
||||
win32Error(env, "CreatePipe");
|
||||
goto Catch;
|
||||
}
|
||||
si.hStdInput = inRead;
|
||||
SetHandleInformation(inWrite, HANDLE_FLAG_INHERIT, FALSE);
|
||||
handles[0] = (jlong) inWrite;
|
||||
}
|
||||
SetHandleInformation(si.hStdInput, HANDLE_FLAG_INHERIT, TRUE);
|
||||
|
||||
if (handles[1] != (jlong) -1) {
|
||||
si.hStdOutput = (HANDLE) handles[1];
|
||||
handles[1] = (jlong) -1;
|
||||
} else {
|
||||
if (! CreatePipe(&outRead, &outWrite, &sa, PIPE_SIZE)) {
|
||||
win32Error(env, "CreatePipe");
|
||||
goto Catch;
|
||||
}
|
||||
si.hStdOutput = outWrite;
|
||||
SetHandleInformation(outRead, HANDLE_FLAG_INHERIT, FALSE);
|
||||
handles[1] = (jlong) outRead;
|
||||
}
|
||||
SetHandleInformation(si.hStdOutput, HANDLE_FLAG_INHERIT, TRUE);
|
||||
|
||||
if (redirectErrorStream) {
|
||||
si.hStdError = si.hStdOutput;
|
||||
handles[2] = (jlong) -1;
|
||||
} else if (handles[2] != (jlong) -1) {
|
||||
si.hStdError = (HANDLE) handles[2];
|
||||
handles[2] = (jlong) -1;
|
||||
} else {
|
||||
if (! CreatePipe(&errRead, &errWrite, &sa, PIPE_SIZE)) {
|
||||
win32Error(env, "CreatePipe");
|
||||
goto Catch;
|
||||
}
|
||||
si.hStdError = errWrite;
|
||||
SetHandleInformation(errRead, HANDLE_FLAG_INHERIT, FALSE);
|
||||
handles[2] = (jlong) errRead;
|
||||
}
|
||||
SetHandleInformation(si.hStdError, HANDLE_FLAG_INHERIT, TRUE);
|
||||
|
||||
if (onNT)
|
||||
processFlag = CREATE_NO_WINDOW | CREATE_UNICODE_ENVIRONMENT;
|
||||
@ -237,9 +268,6 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
|
||||
|
||||
CloseHandle(pi.hThread);
|
||||
ret = (jlong)pi.hProcess;
|
||||
(*env)->SetLongField(env, in_fd, IO_handle_fdID, (jlong)inWrite);
|
||||
(*env)->SetLongField(env, out_fd, IO_handle_fdID, (jlong)outRead);
|
||||
(*env)->SetLongField(env, err_fd, IO_handle_fdID, (jlong)errRead);
|
||||
|
||||
Finally:
|
||||
/* Always clean up the child's side of the pipes */
|
||||
@ -257,6 +285,9 @@ Java_java_lang_ProcessImpl_create(JNIEnv *env, jclass ignored,
|
||||
else
|
||||
JNU_ReleaseStringPlatformChars(env, dir, (char *) penvBlock);
|
||||
}
|
||||
if (handles != NULL)
|
||||
(*env)->ReleaseLongArrayElements(env, stdHandles, handles, 0);
|
||||
|
||||
return ret;
|
||||
|
||||
Catch:
|
||||
|
@ -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 class Basic {
|
||||
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 @@ public class Basic {
|
||||
} 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 @@ public class Basic {
|
||||
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 @@ public class Basic {
|
||||
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 @@ public class Basic {
|
||||
|
||||
}
|
||||
|
||||
static void closeStreams(Process p) {
|
||||
try {
|
||||
p.getOutputStream().close();
|
||||
p.getInputStream().close();
|
||||
p.getErrorStream().close();
|
||||
} catch (Throwable t) { unexpected(t); }
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------
|
||||
// A Policy class designed to make permissions fiddling very easy.
|
||||
//----------------------------------------------------------------
|
||||
@ -1432,10 +1796,19 @@ public class Basic {
|
||||
}
|
||||
} 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;
|
||||
|
Loading…
Reference in New Issue
Block a user