| // 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); | |
| } | |
| } | |
| } |