content: // 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.android.apps.common.testing.broker.shell;
  
  import com.google.android.apps.common.testing.broker.shell.SubprocessBuilder.StreamAction;
  import com.google.common.collect.ImmutableList;
  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(),
              /* killSubprocessOnInterrupt= */ false,
              /* closeOutputStreams= */ 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(),
              /* killSubprocessOnInterrupt= */ false,
              /* closeOutputStreams= */ 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),
              /* killSubprocessOnInterrupt= */ false,
              /* closeOutputStreams= */ 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(),
          /* killSubprocessOnInterrupt= */ false,
          /* closeOutputStreams= */ 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,
          /* killSubprocessOnInterrupt= */ 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 ExecFailedException {
  
      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);
      }
    }
  }
  