aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/shell/Command.java
blob: 8d9cffc838dbbb2df4df129edae5b335d536fb62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
// 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 java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collections;
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];

  private static final String[] EMPTY_STRING_ARRAY = new String[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 ProcessBuilder processBuilder;

  // Start of public API -----------------------------------------------------

  /**
   * Creates a new {@link Command} that will execute a command line that
   * is described by a {@link ProcessBuilder}. Command line elements,
   * environment, and working directory are taken from this object. The
   * command line is executed exactly as given, without a shell.
   *
   * @param processBuilder {@link ProcessBuilder} describing command line
   *  to execute
   */
  public Command(final ProcessBuilder processBuilder) {
    this(processBuilder.command().toArray(EMPTY_STRING_ARRAY),
         processBuilder.environment(),
         processBuilder.directory());
  }

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

  /**
   * Creates a new {@link Command} for the given command line elements. The
   * command line is executed exactly as given, without a shell. The given
   * environment variables and working directory are used in subsequent
   * calls to {@link #execute()}.
   *
   * @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
   * @throws IllegalArgumentException if commandLine is null or empty
   */
  public Command(final String[] commandLineElements,
                 final Map<String, String> environmentVariables,
                 final File workingDirectory) {
    if (commandLineElements == null || commandLineElements.length == 0) {
      throw new IllegalArgumentException("command line is null or empty");
    }
    this.processBuilder =
      new ProcessBuilder(commandLineElements);
    if (environmentVariables != null) {
      // TODO(bazel-team) remove next line eventually; it is here to mimic old
      // Runtime.exec() behavior
      this.processBuilder.environment().clear();
      this.processBuilder.environment().putAll(environmentVariables);
    }
    this.processBuilder.directory(workingDirectory);
  }

  /**
   * @return raw command line elements to be executed
   */
  public String[] getCommandLineElements() {
    final List<String> elements = processBuilder.command();
    return elements.toArray(new String[elements.size()]);
  }

  /**
   * @return (unmodifiable) {@link Map} view of command's environment variables
   */
  public Map<String, String> getEnvironmentVariables() {
    return Collections.unmodifiableMap(processBuilder.environment());
  }

  /**
   * @return working directory used for execution, or null if the current
   *         working directory is used
   */
  public File getWorkingDirectory() {
    return processBuilder.directory();
  }

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

  /**
   * <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 <em>not</em> closed when the
   * process terminates. 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>.
   * @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();
  }

  /**
   * <p>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>.
   * @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)
      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),
        /*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 Process 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() {
        try {
          // exitValue seems to be the only non-blocking call for
          // checking process liveness.
          process.exitValue();
          return true;
        } catch (IllegalThreadStateException e) {
          return false;
        }
      }
    };
  }

  private Process startProcess()
    throws ExecFailedException {
    try {
      return processBuilder.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(final InputSource stdinInput,
                                   final Process 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(final Process process,
                                         final KillableObserver observer) {
    final Killable processKillable = new ProcessKillable(process);
    observer.startObserving(processKillable);
    return processKillable;
  }

  private CommandResult waitForProcessToComplete(
    final Process 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 {
      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);
      }
    }

    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(Process process,
                                       boolean killSubprocessOnInterrupt) {
    boolean wasInterrupted = false;
    try {
      while (true) {
        try {
          return new TerminationStatus(process.waitFor());
        } 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 (final String arg : processBuilder.command()) {
      message.append(" [");
      message.append(arg);
      message.append(']');
    }
    message.append("; environment: ");
    message.append(processBuilder.environment());
    final File workingDirectory = processBuilder.directory();
    message.append("; working dir: ");
    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);
    }
  }
}