blob: d0571154af776d4ef204aa6ac93d75ab0346a5e8 [file] [log] [blame]
// Copyright 2014 The Bazel Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.devtools.build.lib.shell;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.shell.SubprocessBuilder.StreamAction;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* <p>Represents an executable command, including its arguments and
* runtime environment (environment variables, working directory). This class
* lets a caller execute a command, get its results, and optionally try to kill
* the task during execution.</p>
*
* <p>The use of "shell" in the full name of this class is a misnomer. In
* terms of the way its arguments are interpreted, this class is closer to
* {@code execve(2)} than to {@code system(3)}. No Bourne shell is executed.
*
* <p>The most basic use-case for this class is as follows:
* <pre>
* String[] args = { "/bin/du", "-s", directory };
* CommandResult result = new Command(args).execute();
* String output = new String(result.getStdout());
* </pre>
* which writes the output of the {@code du(1)} command into {@code output}.
* More complex cases might inspect the stderr stream, kill the subprocess
* asynchronously, feed input to its standard input, handle the exceptions
* thrown if the command fails, or print the termination status (exit code or
* signal name).
*
* <h4>Invoking the Bourne shell</h4>
*
* <p>Perhaps the most common command invoked programmatically is the UNIX
* shell, {@code /bin/sh}. Because the shell is a general-purpose programming
* language, care must be taken to ensure that variable parts of the shell
* command (e.g. strings entered by the user) do not contain shell
* metacharacters, as this poses a correctness and/or security risk.
*
* <p>To execute a shell command directly, use the following pattern:
* <pre>
* String[] args = { "/bin/sh", "-c", shellCommand };
* CommandResult result = new Command(args).execute();
* </pre>
* {@code shellCommand} is a complete Bourne shell program, possibly containing
* all kinds of unescaped metacharacters. For example, here's a shell command
* that enumerates the working directories of all processes named "foo":
* <pre>ps auxx | grep foo | awk '{print $1}' |
* while read pid; do readlink /proc/$pid/cwd; done</pre>
* It is the responsibility of the caller to ensure that this string means what
* they intend.
*
* <p>Consider the risk posed by allowing the "foo" part of the previous
* command to be some arbitrary (untrusted) string called {@code processName}:
* <pre>
* // WARNING: unsafe!
* String shellCommand = "ps auxx | grep " + processName + " | awk '{print $1}' | "
* + "while read pid; do readlink /proc/$pid/cwd; done";</pre>
* </pre>
* Passing this string to {@link Command} is unsafe because if the string
* {@processName} contains shell metacharacters, the meaning of the command can
* be arbitrarily changed; consider:
* <pre>String processName = ". ; rm -fr $HOME & ";</pre>
*
* <p>To defend against this possibility, it is essential to properly quote the
* variable portions of the shell command so that shell metacharacters are
* escaped. Use {@link ShellUtils#shellEscape} for this purpose:
* <pre>
* // Safe.
* String shellCommand = "ps auxx | grep " + ShellUtils.shellEscape(processName)
* + " | awk '{print $1}' | while read pid; do readlink /proc/$pid/cwd; done";
* </pre>
*
* <p>Tip: if you are only invoking a single known command, and no shell
* features (e.g. $PATH lookup, output redirection, pipelines, etc) are needed,
* call it directly without using a shell, as in the {@code du(1)} example
* above.
*
* <h4>Other features</h4>
*
* <p>A caller can optionally specify bytes to be written to the process's
* "stdin". The returned {@link CommandResult} object gives the caller access to
* the exit status, as well as output from "stdout" and "stderr". To use
* this class with processes that generate very large amounts of input/output,
* consider
* {@link #execute(InputStream, KillableObserver, OutputStream, OutputStream)}
* and
* {@link #execute(byte[], KillableObserver, OutputStream, OutputStream)}.
* </p>
*
* <p>This class ensures that stdout and stderr streams are read promptly,
* avoiding potential deadlock if the output is large. See <a
* href="http://www.javaworld.com/javaworld/jw-12-2000/jw-1229-traps.html"> When
* <code>Runtime.exec()</code> won't</a>.</p>
*
* <p>This class is immutable and therefore thread-safe.</p>
*/
public final class Command {
private static final Logger log =
Logger.getLogger("com.google.devtools.build.lib.shell.Command");
/**
* Pass this value to {@link #execute(byte[])} to indicate that no input
* should be written to stdin.
*/
public static final byte[] NO_INPUT = new byte[0];
/**
* Pass this to {@link #execute(byte[], KillableObserver, boolean)} to
* indicate that you do not wish to observe / kill the underlying
* process.
*/
public static final KillableObserver NO_OBSERVER = new KillableObserver() {
@Override
public void startObserving(final Killable killable) {
// do nothing
}
@Override
public void stopObserving(final Killable killable) {
// do nothing
}
};
private final SubprocessBuilder subprocessBuilder;
// Start of public API -----------------------------------------------------
/**
/**
* Creates a new {@link Command} for the given command line elements. The
* command line is executed exactly as given, without a shell.
* Subsequent calls to {@link #execute()} will use the JVM's working
* directory and environment.
*
* @param commandLineElements elements of raw command line to execute
* @throws IllegalArgumentException if commandLine is null or empty
*/
/* TODO(bazel-team): Use varargs here
*/
public Command(final String[] commandLineElements) {
this(commandLineElements, null, null);
}
/**
* Just like {@link Command(String, Map<String, String>, File, long), but without a timeout.
*/
public Command(
String[] commandLineElements,
Map<String, String> environmentVariables,
File workingDirectory) {
this(commandLineElements, environmentVariables, workingDirectory, -1);
}
/**
* Creates a new {@link Command} for the given command line elements. The
* command line is executed without a shell.
*
* The given environment variables and working directory are used in subsequent
* calls to {@link #execute()}.
*
* This command treats the 0-th element of {@code commandLineElement}
* (the name of an executable to run) specially.
* <ul>
* <li>If it is an absolute path, it is used as it</li>
* <li>If it is a single file name, the PATH lookup is performed</li>
* <li>If it is a relative path that is not a single file name, the command will attempt to
* execute the the binary at that path relative to {@code workingDirectory}.</li>
* </ul>
*
* @param commandLineElements elements of raw command line to execute
* @param environmentVariables environment variables to replace JVM's
* environment variables; may be null
* @param workingDirectory working directory for execution; if null, current
* working directory is used
* @param timeoutMillis timeout in milliseconds. Only supported on Windows.
* @throws IllegalArgumentException if commandLine is null or empty
*/
public Command(
String[] commandLineElements,
Map<String, String> environmentVariables,
File workingDirectory,
long timeoutMillis) {
if (commandLineElements == null || commandLineElements.length == 0) {
throw new IllegalArgumentException("command line is null or empty");
}
File executable = new File(commandLineElements[0]);
if (!executable.isAbsolute() && executable.getParent() != null) {
commandLineElements = commandLineElements.clone();
commandLineElements[0] = new File(workingDirectory, commandLineElements[0]).getAbsolutePath();
}
this.subprocessBuilder = new SubprocessBuilder();
subprocessBuilder.setArgv(ImmutableList.copyOf(commandLineElements));
subprocessBuilder.setEnv(environmentVariables);
subprocessBuilder.setWorkingDirectory(workingDirectory);
subprocessBuilder.setTimeoutMillis(timeoutMillis);
}
/**
* @return raw command line elements to be executed
*/
public String[] getCommandLineElements() {
final List<String> elements = subprocessBuilder.getArgv();
return elements.toArray(new String[elements.size()]);
}
/**
* @return (unmodifiable) {@link Map} view of command's environment variables
*/
public Map<String, String> getEnvironmentVariables() {
return subprocessBuilder.getEnv();
}
/**
* @return working directory used for execution, or null if the current
* working directory is used
*/
public File getWorkingDirectory() {
return subprocessBuilder.getWorkingDirectory();
}
/**
* Execute this command with no input to stdin. This call will block until the
* process completes or an error occurs.
*
* @return {@link CommandResult} representing result of the execution
* @throws ExecFailedException if {@link Runtime#exec(String[])} fails for any
* reason
* @throws AbnormalTerminationException if an {@link IOException} is
* encountered while reading from the process, or the process was terminated
* due to a signal.
* @throws BadExitStatusException if the process exits with a
* non-zero status
*/
public CommandResult execute() throws CommandException {
return execute(NO_INPUT);
}
/**
* Execute this command with given input to stdin. This call will block until
* the process completes or an error occurs.
*
* @param stdinInput bytes to be written to process's stdin
* @return {@link CommandResult} representing result of the execution
* @throws ExecFailedException if {@link Runtime#exec(String[])} fails for any
* reason
* @throws AbnormalTerminationException if an {@link IOException} is
* encountered while reading from the process, or the process was terminated
* due to a signal.
* @throws BadExitStatusException if the process exits with a
* non-zero status
* @throws NullPointerException if stdin is null
*/
public CommandResult execute(final byte[] stdinInput)
throws CommandException {
nullCheck(stdinInput, "stdinInput");
return doExecute(new ByteArrayInputSource(stdinInput),
NO_OBSERVER,
Consumers.createAccumulatingConsumers(),
/*killSubprocess=*/false, /*closeOutput=*/false).get();
}
/**
* <p>Execute this command with given input to stdin. This call will block
* until the process completes or an error occurs. Caller may specify
* whether the method should ignore stdout/stderr output. If the
* given number of milliseconds elapses before the command has
* completed, this method will attempt to kill the command.</p>
*
* @param stdinInput bytes to be written to process's stdin, or
* {@link #NO_INPUT} if no bytes should be written
* @param timeout number of milliseconds to wait for command completion
* before attempting to kill the command
* @param ignoreOutput if true, method will ignore stdout/stderr output
* and return value will not contain this data
* @return {@link CommandResult} representing result of the execution
* @throws ExecFailedException if {@link Runtime#exec(String[])} fails for any
* reason
* @throws AbnormalTerminationException if an {@link IOException} is
* encountered while reading from the process, or the process was terminated
* due to a signal.
* @throws BadExitStatusException if the process exits with a
* non-zero status
* @throws NullPointerException if stdin is null
*/
public CommandResult execute(final byte[] stdinInput,
final long timeout,
final boolean ignoreOutput)
throws CommandException {
return execute(stdinInput,
new TimeoutKillableObserver(timeout),
ignoreOutput);
}
/**
* <p>Execute this command with given input to stdin. This call will block
* until the process completes or an error occurs. Caller may specify
* whether the method should ignore stdout/stderr output. The given {@link
* KillableObserver} may also terminate the process early while running.</p>
*
* @param stdinInput bytes to be written to process's stdin, or
* {@link #NO_INPUT} if no bytes should be written
* @param observer {@link KillableObserver} that should observe the running
* process, or {@link #NO_OBSERVER} if caller does not wish to kill
* the process
* @param ignoreOutput if true, method will ignore stdout/stderr output
* and return value will not contain this data
* @return {@link CommandResult} representing result of the execution
* @throws ExecFailedException if {@link Runtime#exec(String[])} fails for any
* reason
* @throws AbnormalTerminationException if the process is interrupted (or
* killed) before completion, if an {@link IOException} is encountered while
* reading from the process, or the process was terminated due to a signal.
* @throws BadExitStatusException if the process exits with a
* non-zero status
* @throws NullPointerException if stdin is null
*/
public CommandResult execute(final byte[] stdinInput,
final KillableObserver observer,
final boolean ignoreOutput)
throws CommandException {
// supporting "null" here for backwards compatibility
final KillableObserver theObserver =
observer == null ? NO_OBSERVER : observer;
return doExecute(new ByteArrayInputSource(stdinInput),
theObserver,
ignoreOutput ? Consumers.createDiscardingConsumers()
: Consumers.createAccumulatingConsumers(),
/*killSubprocess=*/false, /*closeOutput=*/false).get();
}
/**
* <p>Execute this command with given input to stdin. This call blocks
* until the process completes or an error occurs. The caller provides
* {@link OutputStream} instances into which the process writes its
* stdout/stderr output; these streams are <em>not</em> closed when the
* process terminates. The given {@link KillableObserver} may also
* terminate the process early while running.</p>
*
* <p>Note that stdout and stderr are written concurrently. If these are
* aliased to each other, it is the caller's duty to ensure thread safety.
* </p>
*
* @param stdinInput bytes to be written to process's stdin, or
* {@link #NO_INPUT} if no bytes should be written
* @param observer {@link KillableObserver} that should observe the running
* process, or {@link #NO_OBSERVER} if caller does not wish to kill the
* process
* @param stdOut the process will write its standard output into this stream.
* E.g., you could pass {@link System#out} as <code>stdOut</code>.
* @param stdErr the process will write its standard error into this stream.
* E.g., you could pass {@link System#err} as <code>stdErr</code>.
* @return {@link CommandResult} representing result of the execution. Note
* that {@link CommandResult#getStdout()} and
* {@link CommandResult#getStderr()} will yield {@link IllegalStateException}
* in this case, as the output is written to <code>stdOut/stdErr</code>
* instead.
* @throws ExecFailedException if {@link Runtime#exec(String[])} fails for any
* reason
* @throws AbnormalTerminationException if the process is interrupted (or
* killed) before completion, if an {@link IOException} is encountered while
* reading from the process, or the process was terminated due to a signal.
* @throws BadExitStatusException if the process exits with a
* non-zero status
* @throws NullPointerException if any argument is null.
*/
public CommandResult execute(final byte[] stdinInput,
final KillableObserver observer,
final OutputStream stdOut,
final OutputStream stdErr)
throws CommandException {
return execute(stdinInput, observer, stdOut, stdErr, false);
}
/**
* Like {@link #execute(byte[], KillableObserver, OutputStream, OutputStream)}
* but enables setting of the killSubprocessOnInterrupt attribute.
*
* @param killSubprocessOnInterrupt if set to true, the execution of
* this command is <i>interruptible</i>: in other words, if this thread is
* interrupted during a call to execute, the subprocess will be terminated
* and the call will return in a timely manner. If false, the subprocess
* will run to completion; this is the default value use by all other
* constructors. The thread's interrupted status is preserved in all cases,
* however.
*/
public CommandResult execute(final byte[] stdinInput,
final KillableObserver observer,
final OutputStream stdOut,
final OutputStream stdErr,
final boolean killSubprocessOnInterrupt)
throws CommandException {
nullCheck(stdinInput, "stdinInput");
nullCheck(observer, "observer");
nullCheck(stdOut, "stdOut");
nullCheck(stdErr, "stdErr");
return doExecute(new ByteArrayInputSource(stdinInput),
observer,
Consumers.createStreamingConsumers(stdOut, stdErr),
killSubprocessOnInterrupt, false).get();
}
/**
* Execute this command with given input to stdin; this stream is closed when the process
* terminates, and exceptions raised when closing this stream are ignored. This call blocks until
* the process completes or an error occurs. The caller provides {@link OutputStream} instances
* into which the process writes its stdout/stderr output; these streams are <em>not</em> closed
* when the process terminates. The given {@link KillableObserver} may also terminate the process
* early while running.
*
* <p>If stdOut or stdErr is {@code null}, it will be redirected to /dev/null.
*/
public CommandResult execute(
final byte[] stdinInput,
final KillableObserver observer,
final File stdOut,
final File stdErr,
final boolean killSubprocessOnInterrupt)
throws CommandException {
nullCheck(stdinInput, "stdinInput");
nullCheck(observer, "observer");
if (stdOut == null) {
subprocessBuilder.setStdout(StreamAction.DISCARD);
} else {
subprocessBuilder.setStdout(stdOut);
}
if (stdErr == null) {
subprocessBuilder.setStderr(StreamAction.DISCARD);
} else {
subprocessBuilder.setStderr(stdErr);
}
return doExecute(
new ByteArrayInputSource(stdinInput), observer, null, killSubprocessOnInterrupt, false)
.get();
}
/**
* Execute this command with given input to stdin; this stream is closed when the process
* terminates, and exceptions raised when closing this stream are ignored. This call blocks until
* the process completes or an error occurs. The caller provides {@link OutputStream} instances
* into which the process writes its stdout/stderr output; these streams are <em>not</em> closed
* when the process terminates. The given {@link KillableObserver} may also terminate the process
* early while running.
*
* @param stdinInput The input to this process's stdin
* @param observer {@link KillableObserver} that should observe the running process, or {@link
* #NO_OBSERVER} if caller does not wish to kill the process
* @param stdOut the process will write its standard output into this stream. E.g., you could pass
* {@link System#out} as <code>stdOut</code>.
* @param stdErr the process will write its standard error into this stream. E.g., you could pass
* {@link System#err} as <code>stdErr</code>.
* @return {@link CommandResult} representing result of the execution. Note that {@link
* CommandResult#getStdout()} and {@link CommandResult#getStderr()} will yield {@link
* IllegalStateException} in this case, as the output is written to <code>stdOut/stdErr</code>
* instead.
* @throws ExecFailedException if {@link Runtime#exec(String[])} fails for any reason
* @throws AbnormalTerminationException if the process is interrupted (or killed) before
* completion, if an {@link IOException} is encountered while reading from the process, or the
* process was terminated due to a signal.
* @throws BadExitStatusException if the process exits with a non-zero status
* @throws NullPointerException if any argument is null.
*/
public CommandResult execute(
final InputStream stdinInput,
final KillableObserver observer,
final OutputStream stdOut,
final OutputStream stdErr)
throws CommandException {
nullCheck(stdinInput, "stdinInput");
nullCheck(observer, "observer");
nullCheck(stdOut, "stdOut");
nullCheck(stdErr, "stdErr");
return doExecute(new InputStreamInputSource(stdinInput),
observer,
Consumers.createStreamingConsumers(stdOut, stdErr),
/*killSubprocess=*/false, /*closeOutput=*/false).get();
}
/**
* <p>Execute this command with given input to stdin; this stream is closed
* when the process terminates, and exceptions raised when closing this
* stream are ignored. This call blocks
* until the process completes or an error occurs. The caller provides
* {@link OutputStream} instances into which the process writes its
* stdout/stderr output; these streams are closed when the process terminates
* if closeOut is set. The given {@link KillableObserver} may also
* terminate the process early while running.</p>
*
* @param stdinInput The input to this process's stdin
* @param observer {@link KillableObserver} that should observe the running
* process, or {@link #NO_OBSERVER} if caller does not wish to kill the
* process
* @param stdOut the process will write its standard output into this stream.
* E.g., you could pass {@link System#out} as <code>stdOut</code>.
* @param stdErr the process will write its standard error into this stream.
* E.g., you could pass {@link System#err} as <code>stdErr</code>.
* @param closeOut whether to close the output streams when the subprocess
* terminates.
* @return {@link CommandResult} representing result of the execution. Note
* that {@link CommandResult#getStdout()} and
* {@link CommandResult#getStderr()} will yield {@link IllegalStateException}
* in this case, as the output is written to <code>stdOut/stdErr</code>
* instead.
* @throws ExecFailedException if {@link Runtime#exec(String[])} fails for any
* reason
* @throws AbnormalTerminationException if the process is interrupted (or
* killed) before completion, if an {@link IOException} is encountered while
* reading from the process, or the process was terminated due to a signal.
* @throws BadExitStatusException if the process exits with a
* non-zero status
* @throws NullPointerException if any argument is null.
*/
public CommandResult execute(final InputStream stdinInput,
final KillableObserver observer,
final OutputStream stdOut,
final OutputStream stdErr,
boolean closeOut)
throws CommandException {
nullCheck(stdinInput, "stdinInput");
nullCheck(observer, "observer");
nullCheck(stdOut, "stdOut");
nullCheck(stdErr, "stdErr");
return doExecute(new InputStreamInputSource(stdinInput),
observer,
Consumers.createStreamingConsumers(stdOut, stdErr),
false, closeOut).get();
}
/**
* Executes this command with the given stdinInput, but does not wait for it to complete. The
* caller may choose to observe the status of the launched process by calling methods on the
* returned object.
*
* @param stdinInput bytes to be written to process's stdin, or {@link #NO_INPUT} if no bytes
* should be written
* @return An object that can be used to check if the process terminated and obtain the process
* results.
* @throws ExecFailedException if {@link Runtime#exec(String[])} fails for any reason
* @throws NullPointerException if stdin is null
*/
public FutureCommandResult executeAsynchronously(final byte[] stdinInput)
throws CommandException {
return executeAsynchronously(stdinInput, NO_OBSERVER);
}
/**
* <p>Executes this command with the given input to stdin, but does
* not wait for it to complete. The caller may choose to observe the
* status of the launched process by calling methods on the returned
* object. This method performs the minimum cleanup after the
* process terminates: It closes the input stream, and it ignores
* exceptions that result from closing it. The given {@link
* KillableObserver} may also terminate the process early while
* running.</p>
*
* <p>Note that in this case the {@link KillableObserver} will be assigned
* to start observing the process via
* {@link KillableObserver#startObserving(Killable)} but will only be
* unassigned via {@link KillableObserver#stopObserving(Killable)}, if
* {@link FutureCommandResult#get()} is called. If the
* {@link KillableObserver} implementation used with this method will
* not work correctly without calls to
* {@link KillableObserver#stopObserving(Killable)} then a new instance
* should be used for each call to this method.</p>
*
* @param stdinInput bytes to be written to process's stdin, or
* {@link #NO_INPUT} if no bytes should be written
* @param observer {@link KillableObserver} that should observe the running
* process, or {@link #NO_OBSERVER} if caller does not wish to kill
* the process
* @return An object that can be used to check if the process terminated and
* obtain the process results.
* @throws ExecFailedException if {@link Runtime#exec(String[])} fails for any
* reason
* @throws NullPointerException if stdin is null
*/
public FutureCommandResult executeAsynchronously(final byte[] stdinInput,
final KillableObserver observer)
throws CommandException {
// supporting "null" here for backwards compatibility
final KillableObserver theObserver =
observer == null ? NO_OBSERVER : observer;
nullCheck(stdinInput, "stdinInput");
return doExecute(new ByteArrayInputSource(stdinInput),
theObserver,
Consumers.createDiscardingConsumers(),
/*killSubprocess=*/false, /*closeOutput=*/false);
}
/**
* <p>Executes this command with the given input to stdin, but does
* not wait for it to complete. The caller may choose to observe the
* status of the launched process by calling methods on the returned
* object. This method performs the minimum cleanup after the
* process terminates: It closes the input stream, and it ignores
* exceptions that result from closing it. The caller provides
* {@link OutputStream} instances into which the process writes its
* stdout/stderr output; these streams are <em>not</em> closed when
* the process terminates. The given {@link KillableObserver} may
* also terminate the process early while running.</p>
*
* <p>Note that stdout and stderr are written concurrently. If these are
* aliased to each other, or if the caller continues to write to these
* streams, it is the caller's duty to ensure thread safety.
* </p>
*
* <p>Note that in this case the {@link KillableObserver} will be assigned
* to start observing the process via
* {@link KillableObserver#startObserving(Killable)} but will only be
* unassigned via {@link KillableObserver#stopObserving(Killable)}, if
* {@link FutureCommandResult#get()} is called. If the
* {@link KillableObserver} implementation used with this method will
* not work correctly without calls to
* {@link KillableObserver#stopObserving(Killable)} then a new instance
* should be used for each call to this method.</p>
*
* @param stdinInput The input to this process's stdin
* @param observer {@link KillableObserver} that should observe the running
* process, or {@link #NO_OBSERVER} if caller does not wish to kill
* the process
* @param stdOut the process will write its standard output into this stream.
* E.g., you could pass {@link System#out} as <code>stdOut</code>.
* @param stdErr the process will write its standard error into this stream.
* E.g., you could pass {@link System#err} as <code>stdErr</code>.
* @param killSubprocessOnInterrupt whether or not to kill the created process on interrupt
* @param closeOutput whether to close stdout / stderr when the process closes its output streams.
* @return An object that can be used to check if the process terminated and
* obtain the process results.
* @throws ExecFailedException if {@link Runtime#exec(String[])} fails for any
* reason
* @throws NullPointerException if stdin is null
*/
public FutureCommandResult executeAsynchronously(final InputStream stdinInput,
final KillableObserver observer,
final OutputStream stdOut,
final OutputStream stdErr,
final boolean killSubprocessOnInterrupt,
final boolean closeOutput)
throws CommandException {
// supporting "null" here for backwards compatibility
final KillableObserver theObserver =
observer == null ? NO_OBSERVER : observer;
nullCheck(stdinInput, "stdinInput");
return doExecute(new InputStreamInputSource(stdinInput),
theObserver,
Consumers.createStreamingConsumers(stdOut, stdErr),
killSubprocessOnInterrupt,
closeOutput);
}
public FutureCommandResult executeAsynchronously(final InputStream stdinInput,
final KillableObserver observer,
final OutputStream stdOut,
final OutputStream stdErr)
throws CommandException {
return executeAsynchronously(
stdinInput,
observer,
stdOut,
stdErr,
/*killSubprocess=*/ false,
/*closeOutput=*/ false);
}
// End of public API -------------------------------------------------------
private void nullCheck(Object argument, String argumentName) {
if (argument == null) {
String message = argumentName + " argument must not be null.";
throw new NullPointerException(message);
}
}
private FutureCommandResult doExecute(final InputSource stdinInput,
final KillableObserver observer,
final Consumers.OutErrConsumers outErrConsumers,
final boolean killSubprocessOnInterrupt,
final boolean closeOutputStreams)
throws CommandException {
logCommand();
final Subprocess process = startProcess();
outErrConsumers.logConsumptionStrategy();
outErrConsumers.registerInputs(
process.getInputStream(), process.getErrorStream(), closeOutputStreams);
processInput(stdinInput, process);
// TODO(bazel-team): if the input stream is unbounded, observers will not get start
// notification in a timely manner!
final Killable processKillable = observeProcess(process, observer);
return new FutureCommandResult() {
@Override
public CommandResult get() throws AbnormalTerminationException {
return waitForProcessToComplete(process,
observer,
processKillable,
outErrConsumers,
killSubprocessOnInterrupt);
}
@Override
public boolean isDone() {
return process.finished();
}
};
}
private Subprocess startProcess()
throws ExecFailedException {
try {
return subprocessBuilder.start();
} catch (IOException ioe) {
throw new ExecFailedException(this, ioe);
}
}
private static interface InputSource {
void copyTo(OutputStream out) throws IOException;
boolean isEmpty();
String toLogString(String sourceName);
}
private static class ByteArrayInputSource implements InputSource {
private byte[] bytes;
ByteArrayInputSource(byte[] bytes){
this.bytes = bytes;
}
@Override
public void copyTo(OutputStream out) throws IOException {
out.write(bytes);
out.flush();
}
@Override
public boolean isEmpty() {
return bytes.length == 0;
}
@Override
public String toLogString(String sourceName) {
if (isEmpty()) {
return "No input to " + sourceName;
} else {
return "Input to " + sourceName + ": " +
LogUtil.toTruncatedString(bytes);
}
}
}
private static class InputStreamInputSource implements InputSource {
private InputStream inputStream;
InputStreamInputSource(InputStream inputStream){
this.inputStream = inputStream;
}
@Override
public void copyTo(OutputStream out) throws IOException {
byte[] buf = new byte[4096];
int r;
while ((r = inputStream.read(buf)) != -1) {
out.write(buf, 0, r);
out.flush();
}
}
@Override
public boolean isEmpty() {
return false;
}
@Override
public String toLogString(String sourceName) {
return "Input to " + sourceName + " is a stream.";
}
}
private static void processInput(InputSource stdinInput, Subprocess process) {
if (log.isLoggable(Level.FINER)) {
log.finer(stdinInput.toLogString("stdin"));
}
try {
if (stdinInput.isEmpty()) {
return;
}
stdinInput.copyTo(process.getOutputStream());
} catch (IOException ioe) {
// Note: this is not an error! Perhaps the command just isn't hungry for
// our input and exited with success. Process.waitFor (later) will tell
// us.
//
// (Unlike out/err streams, which are read asynchronously, the input stream is written
// synchronously, in its entirety, before processInput returns. If the input is
// infinite, and is passed through e.g. "cat" subprocess and back into the
// ByteArrayOutputStream, that will eventually run out of memory, causing the output stream
// to be closed, "cat" to terminate with SIGPIPE, and processInput to receive an IOException.
} finally {
// if this statement is ever deleted, the process's outputStream
// must be closed elsewhere -- it is not closed automatically
Command.silentClose(process.getOutputStream());
}
}
private static Killable observeProcess(Subprocess process,
final KillableObserver observer) {
final Killable processKillable = new ProcessKillable(process);
observer.startObserving(processKillable);
return processKillable;
}
private CommandResult waitForProcessToComplete(
final Subprocess process,
final KillableObserver observer,
final Killable processKillable,
final Consumers.OutErrConsumers outErr,
final boolean killSubprocessOnInterrupt)
throws AbnormalTerminationException {
log.finer("Waiting for process...");
TerminationStatus status = waitForProcess(process, killSubprocessOnInterrupt);
observer.stopObserving(processKillable);
log.finer(status.toString());
try {
if (Thread.currentThread().isInterrupted()) {
outErr.cancel();
} else {
outErr.waitForCompletion();
}
} catch (IOException ioe) {
CommandResult noOutputResult =
new CommandResult(CommandResult.EMPTY_OUTPUT,
CommandResult.EMPTY_OUTPUT,
status);
if (status.success()) {
// If command was otherwise successful, throw an exception about this
throw new AbnormalTerminationException(this, noOutputResult, ioe);
} else {
// Otherwise, throw the more important exception -- command
// was not successful
String message = status
+ "; also encountered an error while attempting to retrieve output";
throw status.exited()
? new BadExitStatusException(this, noOutputResult, message, ioe)
: new AbnormalTerminationException(this,
noOutputResult, message, ioe);
}
} finally {
// #close() must be called after the #stopObserving() so that a badly-timed timeout does not
// try to destroy a process that is already closed, and after outErr is completed,
// so that it has a chance to read the entire output is captured.
process.close();
}
CommandResult result =
new CommandResult(outErr.getAccumulatedOut(), outErr.getAccumulatedErr(), status);
result.logThis();
if (status.success()) {
return result;
} else if (status.exited()) {
throw new BadExitStatusException(this, result, status.toString());
} else {
throw new AbnormalTerminationException(this, result, status.toString());
}
}
private static TerminationStatus waitForProcess(Subprocess process,
boolean killSubprocessOnInterrupt) {
boolean wasInterrupted = false;
try {
while (true) {
try {
process.waitFor();
return new TerminationStatus(process.exitValue(), process.timedout());
} catch (InterruptedException ie) {
wasInterrupted = true;
if (killSubprocessOnInterrupt) {
process.destroy();
}
}
}
} finally {
// Read this for detailed explanation: http://www.ibm.com/developerworks/library/j-jtp05236/
if (wasInterrupted) {
Thread.currentThread().interrupt(); // preserve interrupted status
}
}
}
private void logCommand() {
if (!log.isLoggable(Level.FINE)) {
return;
}
log.fine(toDebugString());
}
/**
* A string representation of this command object which includes
* the arguments, the environment, and the working directory. Avoid
* relying on the specifics of this format. Note that the size
* of the result string will reflect the size of the command.
*/
public String toDebugString() {
StringBuilder message = new StringBuilder(128);
message.append("Executing (without brackets):");
for (String arg : subprocessBuilder.getArgv()) {
message.append(" [");
message.append(arg);
message.append(']');
}
message.append("; environment: ");
message.append(subprocessBuilder.getEnv());
message.append("; working dir: ");
File workingDirectory = subprocessBuilder.getWorkingDirectory();
message.append(workingDirectory == null ?
"(current)" :
workingDirectory.toString());
return message.toString();
}
/**
* Close the <code>out</code> stream and log a warning if anything happens.
*/
private static void silentClose(final OutputStream out) {
try {
out.close();
} catch (IOException ioe) {
String message = "Unexpected exception while closing output stream";
log.log(Level.WARNING, message, ioe);
}
}
}