1 /*
  2  * Copyright (c) 2003, 2023, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.  Oracle designates this
  8  * particular file as subject to the "Classpath" exception as provided
  9  * by Oracle in the LICENSE file that accompanied this code.
 10  *
 11  * This code is distributed in the hope that it will be useful, but WITHOUT
 12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 14  * version 2 for more details (a copy is included in the LICENSE file that
 15  * accompanied this code).
 16  *
 17  * You should have received a copy of the GNU General Public License version
 18  * 2 along with this work; if not, write to the Free Software Foundation,
 19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 20  *
 21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 22  * or visit www.oracle.com if you need additional information or have any
 23  * questions.
 24  */
 25 
 26 package java.lang;
 27 
 28 import java.lang.ProcessBuilder.Redirect;
 29 import java.io.BufferedInputStream;
 30 import java.io.BufferedOutputStream;
 31 import java.io.ByteArrayInputStream;
 32 import java.io.FileDescriptor;
 33 import java.io.FileInputStream;
 34 import java.io.FileOutputStream;
 35 import java.io.IOException;
 36 import java.io.InputStream;
 37 import java.io.OutputStream;
 38 import java.nio.charset.Charset;
 39 import java.util.Arrays;
 40 import java.util.Locale;
 41 import java.util.concurrent.CompletableFuture;
 42 import java.util.concurrent.TimeUnit;
 43 import java.util.concurrent.locks.Condition;
 44 import java.util.concurrent.locks.ReentrantLock;
 45 import java.security.AccessController;
 46 import java.security.PrivilegedActionException;
 47 import java.security.PrivilegedExceptionAction;
 48 import jdk.internal.access.JavaIOFileDescriptorAccess;
 49 import jdk.internal.access.SharedSecrets;
 50 import jdk.internal.util.OperatingSystem;
 51 import jdk.internal.util.StaticProperty;
 52 import sun.security.action.GetPropertyAction;
 53 
 54 /**
 55  * java.lang.Process subclass in the UNIX environment.
 56  *
 57  * @author Mario Wolczko and Ross Knippel.
 58  * @author Konstantin Kladko (ported to Linux and Bsd)
 59  * @author Martin Buchholz
 60  * @author Volker Simonis (ported to AIX)
 61  * @since   1.5
 62  */
 63 final class ProcessImpl extends Process {
 64     private static final JavaIOFileDescriptorAccess fdAccess
 65         = SharedSecrets.getJavaIOFileDescriptorAccess();
 66 
 67     // Linux platforms support a normal (non-forcible) kill signal.
 68     static final boolean SUPPORTS_NORMAL_TERMINATION = true;
 69 
 70     // Cache for JNU Charset. The encoding name is guaranteed
 71     // to be supported in this environment.
 72     static final Charset JNU_CHARSET = Charset.forName(StaticProperty.jnuEncoding());
 73 
 74     private final int pid;
 75     private final ProcessHandleImpl processHandle;
 76     private int exitcode;
 77     private boolean hasExited;
 78 
 79     private final ReentrantLock lock = new ReentrantLock();
 80     private final Condition condition = lock.newCondition();
 81 
 82     private /* final */ OutputStream stdin;
 83     private /* final */ InputStream  stdout;
 84     private /* final */ InputStream  stderr;
 85 
 86     private static enum LaunchMechanism {
 87         // order IS important!
 88         FORK,
 89         POSIX_SPAWN,
 90         VFORK
 91     }
 92 
 93     /**
 94      * {@return the default or requested launch mechanism}
 95      * @throws Error if the requested launch mechanism is not found or valid
 96      */
 97     private static LaunchMechanism launchMechanism() {
 98         String s = GetPropertyAction.privilegedGetProperty("jdk.lang.Process.launchMechanism");
 99         if (s == null) {
100             return LaunchMechanism.POSIX_SPAWN;
101         }
102 
103         try {
104             // Should be value of a LaunchMechanism enum
105             LaunchMechanism lm = LaunchMechanism.valueOf(s.toUpperCase(Locale.ROOT));
106             switch (OperatingSystem.current()) {
107                 case LINUX:
108                     return lm;      // All options are valid for Linux
109                 case AIX:
110                 case MACOS:
111                     if (lm != LaunchMechanism.VFORK) {
112                         return lm; // All but VFORK are valid
113                     }
114                     break;
115                 case WINDOWS:
116                     // fall through to throw to Error
117             }
118         } catch (IllegalArgumentException e) {
119         }
120 
121         throw new Error(s + " is not a supported " +
122             "process launch mechanism on this platform: " + OperatingSystem.current());
123     }
124 
125     private static final LaunchMechanism launchMechanism = launchMechanism();
126     private static final byte[] helperpath = toCString(StaticProperty.javaHome() + "/lib/jspawnhelper");
127 
128     private static byte[] toCString(String s) {
129         if (s == null)
130             return null;
131         byte[] bytes = s.getBytes(JNU_CHARSET);
132         byte[] result = new byte[bytes.length + 1];
133         System.arraycopy(bytes, 0,
134                          result, 0,
135                          bytes.length);
136         result[result.length-1] = (byte)0;
137         return result;
138     }
139 
140     // Only for use by ProcessBuilder.start()
141     static Process start(String[] cmdarray,
142                          java.util.Map<String,String> environment,
143                          String dir,
144                          ProcessBuilder.Redirect[] redirects,
145                          boolean redirectErrorStream)
146             throws IOException
147     {
148         assert cmdarray != null && cmdarray.length > 0;
149 
150         // Convert arguments to a contiguous block; it's easier to do
151         // memory management in Java than in C.
152         byte[][] args = new byte[cmdarray.length-1][];
153         int size = args.length; // For added NUL bytes
154         for (int i = 0; i < args.length; i++) {
155             args[i] = cmdarray[i+1].getBytes(JNU_CHARSET);
156             size += args[i].length;
157         }
158         byte[] argBlock = new byte[size];
159         int i = 0;
160         for (byte[] arg : args) {
161             System.arraycopy(arg, 0, argBlock, i, arg.length);
162             i += arg.length + 1;
163             // No need to write NUL bytes explicitly
164         }
165 
166         int[] envc = new int[1];
167         byte[] envBlock = ProcessEnvironment.toEnvironmentBlock(environment, envc);
168 
169         int[] std_fds;
170 
171         FileInputStream  f0 = null;
172         FileOutputStream f1 = null;
173         FileOutputStream f2 = null;
174 
175         try {
176             boolean forceNullOutputStream = false;
177             if (redirects == null) {
178                 std_fds = new int[] { -1, -1, -1 };
179             } else {
180                 std_fds = new int[3];
181 
182                 if (redirects[0] == Redirect.PIPE) {
183                     std_fds[0] = -1;
184                 } else if (redirects[0] == Redirect.INHERIT) {
185                     std_fds[0] = 0;
186                 } else if (redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) {
187                     std_fds[0] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd());
188                 } else {
189                     f0 = new FileInputStream(redirects[0].file());
190                     std_fds[0] = fdAccess.get(f0.getFD());
191                 }
192 
193                 if (redirects[1] == Redirect.PIPE) {
194                     std_fds[1] = -1;
195                 } else if (redirects[1] == Redirect.INHERIT) {
196                     std_fds[1] = 1;
197                 } else if (redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) {
198                     std_fds[1] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd());
199                     // Force getInputStream to return a null stream,
200                     // the fd is directly assigned to the next process.
201                     forceNullOutputStream = true;
202                 } else {
203                     f1 = new FileOutputStream(redirects[1].file(),
204                             redirects[1].append());
205                     std_fds[1] = fdAccess.get(f1.getFD());
206                 }
207 
208                 if (redirects[2] == Redirect.PIPE) {
209                     std_fds[2] = -1;
210                 } else if (redirects[2] == Redirect.INHERIT) {
211                     std_fds[2] = 2;
212                 } else if (redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) {
213                     std_fds[2] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd());
214                 } else {
215                     f2 = new FileOutputStream(redirects[2].file(),
216                             redirects[2].append());
217                     std_fds[2] = fdAccess.get(f2.getFD());
218                 }
219             }
220 
221             Process p = new ProcessImpl
222                     (toCString(cmdarray[0]),
223                             argBlock, args.length,
224                             envBlock, envc[0],
225                             toCString(dir),
226                             std_fds,
227                             forceNullOutputStream,
228                             redirectErrorStream);
229             if (redirects != null) {
230                 // Copy the fd's if they are to be redirected to another process
231                 if (std_fds[0] >= 0 &&
232                         redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) {
233                     fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd(), std_fds[0]);
234                 }
235                 if (std_fds[1] >= 0 &&
236                         redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) {
237                     fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd(), std_fds[1]);
238                 }
239                 if (std_fds[2] >= 0 &&
240                         redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) {
241                     fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd(), std_fds[2]);
242                 }
243             }
244             return p;
245         } finally {
246             // In theory, close() can throw IOException
247             // (although it is rather unlikely to happen here)
248             try { if (f0 != null) f0.close(); }
249             finally {
250                 try { if (f1 != null) f1.close(); }
251                 finally { if (f2 != null) f2.close(); }
252             }
253         }
254     }
255 
256 
257     /**
258      * Creates a process. Depending on the {@code mode} flag, this is done by
259      * one of the following mechanisms:
260      * <pre>
261      *   1 - fork(2) and exec(2)
262      *   2 - posix_spawn(3P)
263      *   3 - vfork(2) and exec(2)
264      * </pre>
265      * @param fds an array of three file descriptors.
266      *        Indexes 0, 1, and 2 correspond to standard input,
267      *        standard output and standard error, respectively.  On
268      *        input, a value of -1 means to create a pipe to connect
269      *        child and parent processes.  On output, a value which
270      *        is not -1 is the parent pipe fd corresponding to the
271      *        pipe which has been created.  An element of this array
272      *        is -1 on input if and only if it is <em>not</em> -1 on
273      *        output.
274      * @return the pid of the subprocess
275      */
276     private native int forkAndExec(int mode, byte[] helperpath,
277                                    byte[] prog,
278                                    byte[] argBlock, int argc,
279                                    byte[] envBlock, int envc,
280                                    byte[] dir,
281                                    int[] fds,
282                                    boolean redirectErrorStream)
283         throws IOException;
284 
285     @SuppressWarnings("removal")
286     private ProcessImpl(final byte[] prog,
287                 final byte[] argBlock, final int argc,
288                 final byte[] envBlock, final int envc,
289                 final byte[] dir,
290                 final int[] fds,
291                 final boolean forceNullOutputStream,
292                 final boolean redirectErrorStream)
293             throws IOException {
294 
295         pid = forkAndExec(launchMechanism.ordinal() + 1,
296                           helperpath,
297                           prog,
298                           argBlock, argc,
299                           envBlock, envc,
300                           dir,
301                           fds,
302                           redirectErrorStream);
303         processHandle = ProcessHandleImpl.getInternal(pid);
304 
305         try {
306             AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
307                 initStreams(fds, forceNullOutputStream);
308                 return null;
309             });
310         } catch (PrivilegedActionException ex) {
311             throw (IOException) ex.getCause();
312         }
313     }
314 
315     static FileDescriptor newFileDescriptor(int fd) {
316         FileDescriptor fileDescriptor = new FileDescriptor();
317         fdAccess.set(fileDescriptor, fd);
318         return fileDescriptor;
319     }
320 
321     /**
322      * Initialize the streams from the file descriptors.
323      * @param fds array of stdin, stdout, stderr fds
324      * @param forceNullOutputStream true if the stdout is being directed to
325      *        a subsequent process. The stdout stream should be a null output stream .
326      * @throws IOException
327      */
328     void initStreams(int[] fds, boolean forceNullOutputStream) throws IOException {
329         switch (OperatingSystem.current()) {
330             case LINUX:
331             case MACOS:
332                 stdin = (fds[0] == -1) ?
333                         ProcessBuilder.NullOutputStream.INSTANCE :
334                         new ProcessPipeOutputStream(fds[0]);
335 
336                 stdout = (fds[1] == -1 || forceNullOutputStream) ?
337                          ProcessBuilder.NullInputStream.INSTANCE :
338                          new ProcessPipeInputStream(fds[1]);
339 
340                 stderr = (fds[2] == -1) ?
341                          ProcessBuilder.NullInputStream.INSTANCE :
342                          new ProcessPipeInputStream(fds[2]);
343 
344                 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
345                     lock.lock();
346                     try {
347                         this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
348                         this.hasExited = true;
349                         condition.signalAll();
350                     } finally {
351                         lock.unlock();
352                     }
353 
354                     if (stdout instanceof ProcessPipeInputStream)
355                         ((ProcessPipeInputStream) stdout).processExited();
356 
357                     if (stderr instanceof ProcessPipeInputStream)
358                         ((ProcessPipeInputStream) stderr).processExited();
359 
360                     if (stdin instanceof ProcessPipeOutputStream)
361                         ((ProcessPipeOutputStream) stdin).processExited();
362 
363                     return null;
364                 });
365                 break;
366 
367             case AIX:
368                 stdin = (fds[0] == -1) ?
369                         ProcessBuilder.NullOutputStream.INSTANCE :
370                         new ProcessPipeOutputStream(fds[0]);
371 
372                 stdout = (fds[1] == -1 || forceNullOutputStream) ?
373                          ProcessBuilder.NullInputStream.INSTANCE :
374                          new DeferredCloseProcessPipeInputStream(fds[1]);
375 
376                 stderr = (fds[2] == -1) ?
377                          ProcessBuilder.NullInputStream.INSTANCE :
378                          new DeferredCloseProcessPipeInputStream(fds[2]);
379 
380                 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
381                     lock.lock();
382                     try {
383                         this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
384                         this.hasExited = true;
385                         condition.signalAll();
386                     } finally {
387                         lock.unlock();
388                     }
389 
390                     if (stdout instanceof DeferredCloseProcessPipeInputStream)
391                         ((DeferredCloseProcessPipeInputStream) stdout).processExited();
392 
393                     if (stderr instanceof DeferredCloseProcessPipeInputStream)
394                         ((DeferredCloseProcessPipeInputStream) stderr).processExited();
395 
396                     if (stdin instanceof ProcessPipeOutputStream)
397                         ((ProcessPipeOutputStream) stdin).processExited();
398 
399                     return null;
400                 });
401                 break;
402 
403             default:
404                 throw new AssertionError("Unsupported platform: " +
405                     OperatingSystem.current());
406         }
407     }
408 
409     public OutputStream getOutputStream() {
410         return stdin;
411     }
412 
413     public InputStream getInputStream() {
414         return stdout;
415     }
416 
417     public InputStream getErrorStream() {
418         return stderr;
419     }
420 
421     public int waitFor() throws InterruptedException {
422         lock.lock();
423         try {
424             while (!hasExited) {
425                 condition.await();
426             }
427             return exitcode;
428         } finally {
429             lock.unlock();
430         }
431     }
432 
433     public boolean waitFor(long timeout, TimeUnit unit)
434         throws InterruptedException
435     {
436         lock.lock();
437         try {
438             long remainingNanos = unit.toNanos(timeout);    // throw NPE before other conditions
439             while (remainingNanos > 0 && !hasExited) {
440                 remainingNanos = condition.awaitNanos(remainingNanos);
441             }
442             return hasExited;
443         } finally {
444             lock.unlock();
445         }
446     }
447 
448     public int exitValue() {
449         lock.lock();
450         try {
451             if (!hasExited) {
452                 throw new IllegalThreadStateException("process hasn't exited");
453             }
454             return exitcode;
455         } finally {
456             lock.unlock();
457         }
458     }
459 
460     private void destroy(boolean force) {
461         switch (OperatingSystem.current()) {
462             case LINUX:
463             case MACOS:
464             case AIX:
465                 // There is a risk that pid will be recycled, causing us to
466                 // kill the wrong process!  So we only terminate processes
467                 // that appear to still be running.  Even with this check,
468                 // there is an unavoidable race condition here, but the window
469                 // is very small, and OSes try hard to not recycle pids too
470                 // soon, so this is quite safe.
471                 lock.lock();
472                 try {
473                     if (!hasExited)
474                         processHandle.destroyProcess(force);
475                 } finally {
476                     lock.unlock();
477                 }
478                 try { stdin.close();  } catch (IOException ignored) {}
479                 try { stdout.close(); } catch (IOException ignored) {}
480                 try { stderr.close(); } catch (IOException ignored) {}
481                 break;
482 
483             default: throw new AssertionError("Unsupported platform: " + OperatingSystem.current());
484         }
485     }
486 
487     @Override
488     public CompletableFuture<Process> onExit() {
489         return ProcessHandleImpl.completion(pid, false)
490                 .handleAsync((unusedExitStatus, unusedThrowable) -> {
491                     boolean interrupted = false;
492                     while (true) {
493                         // Ensure that the concurrent task setting the exit status has completed
494                         try {
495                             waitFor();
496                             break;
497                         } catch (InterruptedException ie) {
498                             interrupted = true;
499                         }
500                     }
501                     if (interrupted) {
502                         Thread.currentThread().interrupt();
503                     }
504                     return this;
505                 });
506     }
507 
508     @Override
509     public ProcessHandle toHandle() {
510         @SuppressWarnings("removal")
511         SecurityManager sm = System.getSecurityManager();
512         if (sm != null) {
513             sm.checkPermission(new RuntimePermission("manageProcess"));
514         }
515         return processHandle;
516     }
517 
518     @Override
519     public boolean supportsNormalTermination() {
520         return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;
521     }
522 
523     @Override
524     public void destroy() {
525         destroy(false);
526     }
527 
528     @Override
529     public Process destroyForcibly() {
530         destroy(true);
531         return this;
532     }
533 
534     @Override
535     public long pid() {
536         return pid;
537     }
538 
539     @Override
540     public boolean isAlive() {
541         lock.lock();
542         try {
543             return !hasExited;
544         } finally {
545             lock.unlock();
546         }
547     }
548 
549     /**
550      * The {@code toString} method returns a string consisting of
551      * the native process ID of the process and the exit value of the process.
552      *
553      * @return a string representation of the object.
554      */
555     @Override
556     public String toString() {
557         return new StringBuilder("Process[pid=").append(pid)
558                 .append(", exitValue=").append(hasExited ? exitcode : "\"not exited\"")
559                 .append("]").toString();
560     }
561 
562     private static native void init();
563 
564     static {
565         init();
566     }
567 
568     /**
569      * A buffered input stream for a subprocess pipe file descriptor
570      * that allows the underlying file descriptor to be reclaimed when
571      * the process exits, via the processExited hook.
572      *
573      * This is tricky because we do not want the user-level InputStream to be
574      * closed until the user invokes close(), and we need to continue to be
575      * able to read any buffered data lingering in the OS pipe buffer.
576      */
577     private static class ProcessPipeInputStream extends BufferedInputStream {
578         private final Object closeLock = new Object();
579 
580         ProcessPipeInputStream(int fd) {
581             super(new PipeInputStream(newFileDescriptor(fd)));
582         }
583         private static byte[] drainInputStream(InputStream in)
584                 throws IOException {
585             int n = 0;
586             int j;
587             byte[] a = null;
588             while ((j = in.available()) > 0) {
589                 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
590                 n += in.read(a, n, j);
591             }
592             return (a == null || n == a.length) ? a : Arrays.copyOf(a, n);
593         }
594 
595         /** Called by the process reaper thread when the process exits. */
596         synchronized void processExited() {
597             synchronized (closeLock) {
598                 try {
599                     InputStream in = this.in;
600                     // this stream is closed if and only if: in == null
601                     if (in != null) {
602                         byte[] stragglers = drainInputStream(in);
603                         in.close();
604                         this.in = (stragglers == null) ?
605                             ProcessBuilder.NullInputStream.INSTANCE :
606                             new ByteArrayInputStream(stragglers);
607                     }
608                 } catch (IOException ignored) {}
609             }
610         }
611 
612         @Override
613         public void close() throws IOException {
614             // BufferedInputStream#close() is not synchronized unlike most other
615             // methods. Synchronizing helps avoid race with processExited().
616             synchronized (closeLock) {
617                 super.close();
618             }
619         }
620     }
621 
622     /**
623      * A buffered output stream for a subprocess pipe file descriptor
624      * that allows the underlying file descriptor to be reclaimed when
625      * the process exits, via the processExited hook.
626      */
627     private static class ProcessPipeOutputStream extends BufferedOutputStream {
628         ProcessPipeOutputStream(int fd) {
629             super(new PipeOutputStream(newFileDescriptor(fd)));
630         }
631 
632         /** Called by the process reaper thread when the process exits. */
633         synchronized void processExited() {
634             OutputStream out = this.out;
635             if (out != null) {
636                 try {
637                     out.close();
638                 } catch (IOException ignored) {
639                     // We know of no reason to get an IOException, but if
640                     // we do, there's nothing else to do but carry on.
641                 }
642                 this.out = ProcessBuilder.NullOutputStream.INSTANCE;
643             }
644         }
645     }
646 
647     /**
648      * A buffered input stream for a subprocess pipe file descriptor
649      * that allows the underlying file descriptor to be reclaimed when
650      * the process exits, via the processExited hook.
651      *
652      * This is tricky because we do not want the user-level InputStream to be
653      * closed until the user invokes close(), and we need to continue to be
654      * able to read any buffered data lingering in the OS pipe buffer.
655      *
656      * On AIX this is especially tricky, because the 'close()' system call
657      * will block if another thread is at the same time blocked in a file
658      * operation (e.g. 'read()') on the same file descriptor. We therefore
659      * combine 'ProcessPipeInputStream' approach used on Linux and Bsd
660      * with the deferring 'close' of InputStream. This means
661      * that every potentially blocking operation on the file descriptor
662      * increments a counter before it is executed and decrements it once it
663      * finishes. The 'close()' operation will only be executed if there are
664      * no pending operations. Otherwise it is deferred after the last pending
665      * operation has finished.
666      *
667      */
668     private static class DeferredCloseProcessPipeInputStream
669         extends BufferedInputStream {
670 
671         private final Object closeLock = new Object();
672         private int useCount = 0;
673         private boolean closePending = false;
674 
675         DeferredCloseProcessPipeInputStream(int fd) {
676             super(new PipeInputStream(newFileDescriptor(fd)));
677         }
678 
679         private InputStream drainInputStream(InputStream in)
680                 throws IOException {
681             int n = 0;
682             int j;
683             byte[] a = null;
684             synchronized (closeLock) {
685                 if (buf == null) // asynchronous close()?
686                     return null; // discard
687                 j = in.available();
688             }
689             while (j > 0) {
690                 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
691                 synchronized (closeLock) {
692                     if (buf == null) // asynchronous close()?
693                         return null; // discard
694                     n += in.read(a, n, j);
695                     j = in.available();
696                 }
697             }
698             return (a == null) ?
699                     ProcessBuilder.NullInputStream.INSTANCE :
700                     new ByteArrayInputStream(n == a.length ? a : Arrays.copyOf(a, n));
701         }
702 
703         /** Called by the process reaper thread when the process exits. */
704         synchronized void processExited() {
705             try {
706                 InputStream in = this.in;
707                 if (in != null) {
708                     InputStream stragglers = drainInputStream(in);
709                     in.close();
710                     this.in = stragglers;
711                 }
712             } catch (IOException ignored) { }
713         }
714 
715         private void raise() {
716             synchronized (closeLock) {
717                 useCount++;
718             }
719         }
720 
721         private void lower() throws IOException {
722             synchronized (closeLock) {
723                 useCount--;
724                 if (useCount == 0 && closePending) {
725                     closePending = false;
726                     super.close();
727                 }
728             }
729         }
730 
731         @Override
732         public int read() throws IOException {
733             raise();
734             try {
735                 return super.read();
736             } finally {
737                 lower();
738             }
739         }
740 
741         @Override
742         public int read(byte[] b) throws IOException {
743             raise();
744             try {
745                 return super.read(b);
746             } finally {
747                 lower();
748             }
749         }
750 
751         @Override
752         public int read(byte[] b, int off, int len) throws IOException {
753             raise();
754             try {
755                 return super.read(b, off, len);
756             } finally {
757                 lower();
758             }
759         }
760 
761         @Override
762         public long skip(long n) throws IOException {
763             raise();
764             try {
765                 return super.skip(n);
766             } finally {
767                 lower();
768             }
769         }
770 
771         @Override
772         public int available() throws IOException {
773             raise();
774             try {
775                 return super.available();
776             } finally {
777                 lower();
778             }
779         }
780 
781         @Override
782         public void close() throws IOException {
783             // BufferedInputStream#close() is not synchronized unlike most other
784             // methods. Synchronizing helps avoid racing with drainInputStream().
785             synchronized (closeLock) {
786                 if (useCount == 0) {
787                     super.close();
788                 }
789                 else {
790                     closePending = true;
791                 }
792             }
793         }
794     }
795 }
--- EOF ---