/* * Copyright (c) 2003, 2010, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package java.lang; import java.io.File; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.AccessControlException; import java.util.Arrays; import java.util.ArrayList; import java.util.List; import java.util.Map; /** * This class is used to create operating system processes. * *
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 * to create new subprocesses with identical or related attributes. * *
Each process builder manages these process attributes: * *
Modifying a process builder's attributes will affect processes * subsequently started by that object's {@link #start()} method, but * will never affect previously started processes or the Java process * itself. * *
Most error checking is performed by the {@link #start()} method. * It is possible to modify the state of an object so that {@link * #start()} will fail. For example, setting the command attribute to * an empty list will not throw an exception unless {@link #start()} * is invoked. * *
Note that this class is not synchronized. * If multiple threads access a {@code ProcessBuilder} instance * concurrently, and at least one of the threads modifies one of the * attributes structurally, it must be synchronized externally. * *
Starting a new process which uses the default working directory * and environment is easy: * *
{@code * Process p = new ProcessBuilder("myCommand", "myArg").start(); * }* *
Here is an example that starts a process with a modified working * directory and environment: * *
{@code * ProcessBuilder pb = * new ProcessBuilder("myCommand", "myArg1", "myArg2"); * Map* *env = pb.environment(); * env.put("VAR1", "myValue"); * env.remove("OTHERVAR"); * env.put("VAR2", env.get("VAR1") + "suffix"); * pb.directory(new File("myDir")); * Process p = pb.start(); * }
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
*/
public final class ProcessBuilder
{
private List 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} instances always
* contain independent process environments, so changes to the
* returned map will never be reflected in any other
* {@code ProcessBuilder} instance or the values returned by
* {@link System#getenv System.getenv}.
*
* If the system does not support environment variables, an
* empty map is returned.
*
* The returned map does not permit null keys or values.
* Attempting to insert or query the presence of a null key or
* value will throw a {@link NullPointerException}.
* Attempting to query the presence of a key or value which is not
* of type {@link String} will throw a {@link ClassCastException}.
*
* The behavior of the returned map is system-dependent. A
* system may not allow modifications to environment variables or
* may forbid certain variable names or values. For this reason,
* attempts to modify the map may fail with
* {@link UnsupportedOperationException} or
* {@link IllegalArgumentException}
* if the modification is not permitted by the operating system.
*
* Since the external format of environment variable names and
* values is system-dependent, there may not be a one-to-one
* mapping between them and Java's Unicode strings. Nevertheless,
* the map is implemented in such a way that environment variables
* which are not modified by Java code will have an unmodified
* native representation in the subprocess.
*
* The returned map and its collection views may not obey the
* general contract of the {@link Object#equals} and
* {@link Object#hashCode} methods.
*
* The returned map is typically case-sensitive on all platforms.
*
* If a security manager exists, its
* {@link SecurityManager#checkPermission checkPermission} method
* is called with a
* {@link RuntimePermission}{@code ("getenv.*")} permission.
* This may result in a {@link SecurityException} being thrown.
*
* When passing information to a Java subprocess,
* system properties
* are generally preferred over environment variables.
*
* @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
*
* @see Runtime#exec(String[],String[],java.io.File)
* @see System#getenv()
*/
public Map Each of the above categories has an associated unique
* {@link Type Type}.
*
* @since 1.7
*
* @hide
*/
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.
*
* It will always be true that
* It will always be true that
* It will always be true that
* It will always be true that
* It will always be true that
* 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
* null output stream.
*
* @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
*
* @hide
*/
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.
*
* 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
* null input stream.
*
* @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
*
* @hide
*/
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.
*
* 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
* null input stream.
*
* 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
*
* @hide
*/
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.
*
* 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
*
* @hide
*/
public ProcessBuilder redirectInput(File file) {
return redirectInput(Redirect.from(file));
}
/**
* Sets this process builder's standard output destination to a file.
*
* 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
*
* @hide
*/
public ProcessBuilder redirectOutput(File file) {
return redirectOutput(Redirect.to(file));
}
/**
* Sets this process builder's standard error destination to a file.
*
* 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
*
* @hide
*/
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
*
* @hide
*/
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
*
* @hide
*/
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
*
* @hide
*/
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.
*
* This is a convenience method. An invocation of the form
* 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}.
*
* @return this process builder's {@code redirectErrorStream} property
*/
public boolean redirectErrorStream() {
return redirectErrorStream;
}
/**
* Sets this process builder's {@code redirectErrorStream} property.
*
* 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}.
*
* @param redirectErrorStream the new property value
* @return this process builder
*/
public ProcessBuilder redirectErrorStream(boolean redirectErrorStream) {
this.redirectErrorStream = redirectErrorStream;
return this;
}
/**
* Starts a new process using the attributes of this process builder.
*
* The new process will
* invoke the command and arguments given by {@link #command()},
* in a working directory as given by {@link #directory()},
* with a process environment as given by {@link #environment()}.
*
* This method checks that the command is a valid operating
* system command. Which commands are valid is system-dependent,
* but at the very least the command must be a non-empty list of
* non-null strings.
*
* A minimal set of system dependent environment variables may
* be required to start a process on some operating systems.
* As a result, the subprocess may inherit additional environment variable
* settings beyond those in the process builder's {@link #environment()}.
*
* If there is a security manager, its
* {@link SecurityManager#checkExec checkExec}
* method is called with the first component of this object's
* {@code command} array as its argument. This may result in
* a {@link SecurityException} being thrown.
*
* Starting an operating system process is highly system-dependent.
* Among the many things that can go wrong are:
* In such cases an exception will be thrown. The exact nature
* of the exception is system-dependent, but it will always be a
* subclass of {@link IOException}.
*
* Subsequent modifications to this process builder will not
* affect the returned {@link Process}.
*
* @return a new {@link Process} object for managing the subprocess
*
* @throws NullPointerException
* if an element of the command list is null
*
* @throws IndexOutOfBoundsException
* if the command is an empty list (has size {@code 0})
*
* @throws SecurityException
* if a security manager exists and
*
*
*
* {@code
* Redirect.PIPE.file() == null &&
* Redirect.PIPE.type() == Redirect.Type.PIPE
* }
*/
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).
*
* {@code
* Redirect.INHERIT.file() == null &&
* Redirect.INHERIT.type() == Redirect.Type.INHERIT
* }
*/
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; }
/**
* When redirected to a destination file, indicates if the output
* is to be written to the end of the file.
*/
boolean append() {
throw new UnsupportedOperationException();
}
/**
* Returns a redirect to read from the specified file.
*
* {@code
* Redirect.from(file).file() == file &&
* Redirect.from(file).type() == Redirect.Type.READ
* }
*
* @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.
*
* {@code
* Redirect.to(file).file() == file &&
* Redirect.to(file).type() == Redirect.Type.WRITE
* }
*
* @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 + "\"";
}
boolean append() { return 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.
*
* {@code
* Redirect.appendTo(file).file() == file &&
* Redirect.appendTo(file).type() == Redirect.Type.APPEND
* }
*
* @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 + "\"";
}
boolean append() { return 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.
*
* {@code
* pb.inheritIO()
* }
* behaves in exactly the same way as the invocation
* {@code
* pb.redirectInput(Redirect.INHERIT)
* .redirectOutput(Redirect.INHERIT)
* .redirectError(Redirect.INHERIT)
* }
*
* 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
*
* @hide
*/
public ProcessBuilder inheritIO() {
Arrays.fill(redirects(), Redirect.INHERIT);
return this;
}
/**
* Tells whether this process builder merges standard error and
* standard output.
*
*
*
*
*
*
*
*
* @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
// list might try to circumvent the security check.
String[] cmdarray = command.toArray(new String[command.size()]);
cmdarray = cmdarray.clone();
for (String arg : cmdarray)
if (arg == null)
throw new NullPointerException();
// Throws IndexOutOfBoundsException if command is empty
String prog = cmdarray[0];
SecurityManager security = System.getSecurityManager();
if (security != null) {
security.checkExec(prog);
}
String dir = directory == null ? null : directory.toString();
try {
return ProcessImpl.start(cmdarray,
environment,
dir,
redirects,
redirectErrorStream);
} catch (IOException | IllegalArgumentException e) {
String exceptionInfo = ": " + e.getMessage();
Throwable cause = e;
if ((e instanceof IOException) && security != null) {
// Can not disclose the fail reason for read-protected files.
try {
security.checkRead(prog);
} catch (AccessControlException ace) {
exceptionInfo = "";
cause = ace;
}
}
// It's much easier for us to create a high-quality error
// message than the low-level C code which found the problem.
throw new IOException(
"Cannot run program \"" + prog + "\""
+ (dir == null ? "" : " (in directory \"" + dir + "\")")
+ exceptionInfo,
cause);
}
}
}