1 /*
  2  * Copyright (c) 2003, 2021, 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.util.Arrays;
 39 import java.util.EnumSet;
 40 import java.util.Locale;
 41 import java.util.Set;
 42 import java.util.concurrent.CompletableFuture;
 43 import java.util.concurrent.TimeUnit;
 44 import java.security.AccessController;
 45 import java.security.PrivilegedAction;
 46 import java.security.PrivilegedActionException;
 47 import java.security.PrivilegedExceptionAction;
 48 import java.util.Properties;
 49 import jdk.internal.access.JavaIOFileDescriptorAccess;
 50 import jdk.internal.access.SharedSecrets;
 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     private final int pid;
 71     private final ProcessHandleImpl processHandle;
 72     private int exitcode;
 73     private boolean hasExited;
 74 
 75     private /* final */ OutputStream stdin;
 76     private /* final */ InputStream  stdout;
 77     private /* final */ InputStream  stderr;
 78 
 79     private static enum LaunchMechanism {
 80         // order IS important!
 81         FORK,
 82         POSIX_SPAWN,
 83         VFORK
 84     }
 85 
 86     private static enum Platform {
 87 
 88         LINUX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.VFORK, LaunchMechanism.FORK),
 89 
 90         BSD(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK),
 91 
 92         AIX(LaunchMechanism.POSIX_SPAWN, LaunchMechanism.FORK);
 93 
 94         final LaunchMechanism defaultLaunchMechanism;
 95         final Set<LaunchMechanism> validLaunchMechanisms;
 96 
 97         Platform(LaunchMechanism ... launchMechanisms) {
 98             this.defaultLaunchMechanism = launchMechanisms[0];
 99             this.validLaunchMechanisms =
100                 EnumSet.copyOf(Arrays.asList(launchMechanisms));
101         }
102 
103         @SuppressWarnings("removal")
104         LaunchMechanism launchMechanism() {
105             return AccessController.doPrivileged(
106                 (PrivilegedAction<LaunchMechanism>) () -> {
107                     String s = System.getProperty(
108                         "jdk.lang.Process.launchMechanism");
109                     LaunchMechanism lm;
110                     if (s == null) {
111                         lm = defaultLaunchMechanism;
112                         s = lm.name().toLowerCase(Locale.ENGLISH);
113                     } else {
114                         try {
115                             lm = LaunchMechanism.valueOf(
116                                 s.toUpperCase(Locale.ENGLISH));
117                         } catch (IllegalArgumentException e) {
118                             lm = null;
119                         }
120                     }
121                     if (lm == null || !validLaunchMechanisms.contains(lm)) {
122                         throw new Error(
123                             s + " is not a supported " +
124                             "process launch mechanism on this platform."
125                         );
126                     }
127                     return lm;
128                 }
129             );
130         }
131 
132         static Platform get() {
133             String osName = GetPropertyAction.privilegedGetProperty("os.name");
134 
135             if (osName.equals("Linux")) { return LINUX; }
136             if (osName.contains("OS X")) { return BSD; }
137             if (osName.equals("AIX")) { return AIX; }
138 
139             throw new Error(osName + " is not a supported OS platform.");
140         }
141     }
142 
143     private static final Platform platform = Platform.get();
144     private static final LaunchMechanism launchMechanism = platform.launchMechanism();
145     private static final byte[] helperpath = toCString(StaticProperty.javaHome() + "/lib/jspawnhelper");
146 
147     private static byte[] toCString(String s) {
148         if (s == null)
149             return null;
150         byte[] bytes = s.getBytes();
151         byte[] result = new byte[bytes.length + 1];
152         System.arraycopy(bytes, 0,
153                          result, 0,
154                          bytes.length);
155         result[result.length-1] = (byte)0;
156         return result;
157     }
158 
159     // Only for use by ProcessBuilder.start()
160     static Process start(String[] cmdarray,
161                          java.util.Map<String,String> environment,
162                          String dir,
163                          ProcessBuilder.Redirect[] redirects,
164                          boolean redirectErrorStream)
165             throws IOException
166     {
167         assert cmdarray != null && cmdarray.length > 0;
168 
169         // Convert arguments to a contiguous block; it's easier to do
170         // memory management in Java than in C.
171         byte[][] args = new byte[cmdarray.length-1][];
172         int size = args.length; // For added NUL bytes
173         for (int i = 0; i < args.length; i++) {
174             args[i] = cmdarray[i+1].getBytes();
175             size += args[i].length;
176         }
177         byte[] argBlock = new byte[size];
178         int i = 0;
179         for (byte[] arg : args) {
180             System.arraycopy(arg, 0, argBlock, i, arg.length);
181             i += arg.length + 1;
182             // No need to write NUL bytes explicitly
183         }
184 
185         int[] envc = new int[1];
186         byte[] envBlock = ProcessEnvironment.toEnvironmentBlock(environment, envc);
187 
188         int[] std_fds;
189 
190         FileInputStream  f0 = null;
191         FileOutputStream f1 = null;
192         FileOutputStream f2 = null;
193 
194         try {
195             boolean forceNullOutputStream = false;
196             if (redirects == null) {
197                 std_fds = new int[] { -1, -1, -1 };
198             } else {
199                 std_fds = new int[3];
200 
201                 if (redirects[0] == Redirect.PIPE) {
202                     std_fds[0] = -1;
203                 } else if (redirects[0] == Redirect.INHERIT) {
204                     std_fds[0] = 0;
205                 } else if (redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) {
206                     std_fds[0] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd());
207                 } else {
208                     f0 = new FileInputStream(redirects[0].file());
209                     std_fds[0] = fdAccess.get(f0.getFD());
210                 }
211 
212                 if (redirects[1] == Redirect.PIPE) {
213                     std_fds[1] = -1;
214                 } else if (redirects[1] == Redirect.INHERIT) {
215                     std_fds[1] = 1;
216                 } else if (redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) {
217                     std_fds[1] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd());
218                     // Force getInputStream to return a null stream,
219                     // the fd is directly assigned to the next process.
220                     forceNullOutputStream = true;
221                 } else {
222                     f1 = new FileOutputStream(redirects[1].file(),
223                             redirects[1].append());
224                     std_fds[1] = fdAccess.get(f1.getFD());
225                 }
226 
227                 if (redirects[2] == Redirect.PIPE) {
228                     std_fds[2] = -1;
229                 } else if (redirects[2] == Redirect.INHERIT) {
230                     std_fds[2] = 2;
231                 } else if (redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) {
232                     std_fds[2] = fdAccess.get(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd());
233                 } else {
234                     f2 = new FileOutputStream(redirects[2].file(),
235                             redirects[2].append());
236                     std_fds[2] = fdAccess.get(f2.getFD());
237                 }
238             }
239 
240             Process p = new ProcessImpl
241                     (toCString(cmdarray[0]),
242                             argBlock, args.length,
243                             envBlock, envc[0],
244                             toCString(dir),
245                             std_fds,
246                             forceNullOutputStream,
247                             redirectErrorStream);
248             if (redirects != null) {
249                 // Copy the fd's if they are to be redirected to another process
250                 if (std_fds[0] >= 0 &&
251                         redirects[0] instanceof ProcessBuilder.RedirectPipeImpl) {
252                     fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[0]).getFd(), std_fds[0]);
253                 }
254                 if (std_fds[1] >= 0 &&
255                         redirects[1] instanceof ProcessBuilder.RedirectPipeImpl) {
256                     fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[1]).getFd(), std_fds[1]);
257                 }
258                 if (std_fds[2] >= 0 &&
259                         redirects[2] instanceof ProcessBuilder.RedirectPipeImpl) {
260                     fdAccess.set(((ProcessBuilder.RedirectPipeImpl) redirects[2]).getFd(), std_fds[2]);
261                 }
262             }
263             return p;
264         } finally {
265             // In theory, close() can throw IOException
266             // (although it is rather unlikely to happen here)
267             try { if (f0 != null) f0.close(); }
268             finally {
269                 try { if (f1 != null) f1.close(); }
270                 finally { if (f2 != null) f2.close(); }
271             }
272         }
273     }
274 
275 
276     /**
277      * Creates a process. Depending on the {@code mode} flag, this is done by
278      * one of the following mechanisms:
279      * <pre>
280      *   1 - fork(2) and exec(2)
281      *   2 - posix_spawn(3P)
282      *   3 - vfork(2) and exec(2)
283      * </pre>
284      * @param fds an array of three file descriptors.
285      *        Indexes 0, 1, and 2 correspond to standard input,
286      *        standard output and standard error, respectively.  On
287      *        input, a value of -1 means to create a pipe to connect
288      *        child and parent processes.  On output, a value which
289      *        is not -1 is the parent pipe fd corresponding to the
290      *        pipe which has been created.  An element of this array
291      *        is -1 on input if and only if it is <em>not</em> -1 on
292      *        output.
293      * @return the pid of the subprocess
294      */
295     private native int forkAndExec(int mode, byte[] helperpath,
296                                    byte[] prog,
297                                    byte[] argBlock, int argc,
298                                    byte[] envBlock, int envc,
299                                    byte[] dir,
300                                    int[] fds,
301                                    boolean redirectErrorStream)
302         throws IOException;
303 
304     @SuppressWarnings("removal")
305     private ProcessImpl(final byte[] prog,
306                 final byte[] argBlock, final int argc,
307                 final byte[] envBlock, final int envc,
308                 final byte[] dir,
309                 final int[] fds,
310                 final boolean forceNullOutputStream,
311                 final boolean redirectErrorStream)
312             throws IOException {
313 
314         pid = forkAndExec(launchMechanism.ordinal() + 1,
315                           helperpath,
316                           prog,
317                           argBlock, argc,
318                           envBlock, envc,
319                           dir,
320                           fds,
321                           redirectErrorStream);
322         processHandle = ProcessHandleImpl.getInternal(pid);
323 
324         try {
325             AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
326                 initStreams(fds, forceNullOutputStream);
327                 return null;
328             });
329         } catch (PrivilegedActionException ex) {
330             throw (IOException) ex.getCause();
331         }
332     }
333 
334     static FileDescriptor newFileDescriptor(int fd) {
335         FileDescriptor fileDescriptor = new FileDescriptor();
336         fdAccess.set(fileDescriptor, fd);
337         return fileDescriptor;
338     }
339 
340     /**
341      * Initialize the streams from the file descriptors.
342      * @param fds array of stdin, stdout, stderr fds
343      * @param forceNullOutputStream true if the stdout is being directed to
344      *        a subsequent process. The stdout stream should be a null output stream .
345      * @throws IOException
346      */
347     void initStreams(int[] fds, boolean forceNullOutputStream) throws IOException {
348         switch (platform) {
349             case LINUX:
350             case BSD:
351                 stdin = (fds[0] == -1) ?
352                         ProcessBuilder.NullOutputStream.INSTANCE :
353                         new ProcessPipeOutputStream(fds[0]);
354 
355                 stdout = (fds[1] == -1 || forceNullOutputStream) ?
356                          ProcessBuilder.NullInputStream.INSTANCE :
357                          new ProcessPipeInputStream(fds[1]);
358 
359                 stderr = (fds[2] == -1) ?
360                          ProcessBuilder.NullInputStream.INSTANCE :
361                          new ProcessPipeInputStream(fds[2]);
362 
363                 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
364                     synchronized (this) {
365                         this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
366                         this.hasExited = true;
367                         this.notifyAll();
368                     }
369 
370                     if (stdout instanceof ProcessPipeInputStream)
371                         ((ProcessPipeInputStream) stdout).processExited();
372 
373                     if (stderr instanceof ProcessPipeInputStream)
374                         ((ProcessPipeInputStream) stderr).processExited();
375 
376                     if (stdin instanceof ProcessPipeOutputStream)
377                         ((ProcessPipeOutputStream) stdin).processExited();
378 
379                     return null;
380                 });
381                 break;
382 
383             case AIX:
384                 stdin = (fds[0] == -1) ?
385                         ProcessBuilder.NullOutputStream.INSTANCE :
386                         new ProcessPipeOutputStream(fds[0]);
387 
388                 stdout = (fds[1] == -1 || forceNullOutputStream) ?
389                          ProcessBuilder.NullInputStream.INSTANCE :
390                          new DeferredCloseProcessPipeInputStream(fds[1]);
391 
392                 stderr = (fds[2] == -1) ?
393                          ProcessBuilder.NullInputStream.INSTANCE :
394                          new DeferredCloseProcessPipeInputStream(fds[2]);
395 
396                 ProcessHandleImpl.completion(pid, true).handle((exitcode, throwable) -> {
397                     synchronized (this) {
398                         this.exitcode = (exitcode == null) ? -1 : exitcode.intValue();
399                         this.hasExited = true;
400                         this.notifyAll();
401                     }
402 
403                     if (stdout instanceof DeferredCloseProcessPipeInputStream)
404                         ((DeferredCloseProcessPipeInputStream) stdout).processExited();
405 
406                     if (stderr instanceof DeferredCloseProcessPipeInputStream)
407                         ((DeferredCloseProcessPipeInputStream) stderr).processExited();
408 
409                     if (stdin instanceof ProcessPipeOutputStream)
410                         ((ProcessPipeOutputStream) stdin).processExited();
411 
412                     return null;
413                 });
414                 break;
415 
416             default: throw new AssertionError("Unsupported platform: " + platform);
417         }
418     }
419 
420     public OutputStream getOutputStream() {
421         return stdin;
422     }
423 
424     public InputStream getInputStream() {
425         return stdout;
426     }
427 
428     public InputStream getErrorStream() {
429         return stderr;
430     }
431 
432     public synchronized int waitFor() throws InterruptedException {
433         while (!hasExited) {
434             wait();
435         }
436         return exitcode;
437     }
438 
439     @Override
440     public synchronized boolean waitFor(long timeout, TimeUnit unit)
441         throws InterruptedException
442     {
443         long remainingNanos = unit.toNanos(timeout);    // throw NPE before other conditions
444         if (hasExited) return true;
445         if (timeout <= 0) return false;
446 
447         long deadline = System.nanoTime() + remainingNanos;
448         do {
449             TimeUnit.NANOSECONDS.timedWait(this, remainingNanos);
450             if (hasExited) {
451                 return true;
452             }
453             remainingNanos = deadline - System.nanoTime();
454         } while (remainingNanos > 0);
455         return hasExited;
456     }
457 
458     public synchronized int exitValue() {
459         if (!hasExited) {
460             throw new IllegalThreadStateException("process hasn't exited");
461         }
462         return exitcode;
463     }
464 
465     private void destroy(boolean force) {
466         switch (platform) {
467             case LINUX:
468             case BSD:
469             case AIX:
470                 // There is a risk that pid will be recycled, causing us to
471                 // kill the wrong process!  So we only terminate processes
472                 // that appear to still be running.  Even with this check,
473                 // there is an unavoidable race condition here, but the window
474                 // is very small, and OSes try hard to not recycle pids too
475                 // soon, so this is quite safe.
476                 synchronized (this) {
477                     if (!hasExited)
478                         processHandle.destroyProcess(force);
479                 }
480                 try { stdin.close();  } catch (IOException ignored) {}
481                 try { stdout.close(); } catch (IOException ignored) {}
482                 try { stderr.close(); } catch (IOException ignored) {}
483                 break;
484 
485             default: throw new AssertionError("Unsupported platform: " + platform);
486         }
487     }
488 
489     @Override
490     public CompletableFuture<Process> onExit() {
491         return ProcessHandleImpl.completion(pid, false)
492                 .handleAsync((unusedExitStatus, unusedThrowable) -> {
493                     boolean interrupted = false;
494                     while (true) {
495                         // Ensure that the concurrent task setting the exit status has completed
496                         try {
497                             waitFor();
498                             break;
499                         } catch (InterruptedException ie) {
500                             interrupted = true;
501                         }
502                     }
503                     if (interrupted) {
504                         Thread.currentThread().interrupt();
505                     }
506                     return this;
507                 });
508     }
509 
510     @Override
511     public ProcessHandle toHandle() {
512         @SuppressWarnings("removal")
513         SecurityManager sm = System.getSecurityManager();
514         if (sm != null) {
515             sm.checkPermission(new RuntimePermission("manageProcess"));
516         }
517         return processHandle;
518     }
519 
520     @Override
521     public boolean supportsNormalTermination() {
522         return ProcessImpl.SUPPORTS_NORMAL_TERMINATION;
523     }
524 
525     @Override
526     public void destroy() {
527         destroy(false);
528     }
529 
530     @Override
531     public Process destroyForcibly() {
532         destroy(true);
533         return this;
534     }
535 
536     @Override
537     public long pid() {
538         return pid;
539     }
540 
541     @Override
542     public synchronized boolean isAlive() {
543         return !hasExited;
544     }
545 
546     /**
547      * The {@code toString} method returns a string consisting of
548      * the native process ID of the process and the exit value of the process.
549      *
550      * @return a string representation of the object.
551      */
552     @Override
553     public String toString() {
554         return new StringBuilder("Process[pid=").append(pid)
555                 .append(", exitValue=").append(hasExited ? exitcode : "\"not exited\"")
556                 .append("]").toString();
557     }
558 
559     private static native void init();
560 
561     static {
562         init();
563     }
564 
565     /**
566      * A buffered input stream for a subprocess pipe file descriptor
567      * that allows the underlying file descriptor to be reclaimed when
568      * the process exits, via the processExited hook.
569      *
570      * This is tricky because we do not want the user-level InputStream to be
571      * closed until the user invokes close(), and we need to continue to be
572      * able to read any buffered data lingering in the OS pipe buffer.
573      */
574     private static class ProcessPipeInputStream extends BufferedInputStream {
575         private final Object closeLock = new Object();
576 
577         ProcessPipeInputStream(int fd) {
578             super(new PipeInputStream(newFileDescriptor(fd)));
579         }
580         private static byte[] drainInputStream(InputStream in)
581                 throws IOException {
582             int n = 0;
583             int j;
584             byte[] a = null;
585             while ((j = in.available()) > 0) {
586                 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
587                 n += in.read(a, n, j);
588             }
589             return (a == null || n == a.length) ? a : Arrays.copyOf(a, n);
590         }
591 
592         /** Called by the process reaper thread when the process exits. */
593         synchronized void processExited() {
594             synchronized (closeLock) {
595                 try {
596                     InputStream in = this.in;
597                     // this stream is closed if and only if: in == null
598                     if (in != null) {
599                         byte[] stragglers = drainInputStream(in);
600                         in.close();
601                         this.in = (stragglers == null) ?
602                             ProcessBuilder.NullInputStream.INSTANCE :
603                             new ByteArrayInputStream(stragglers);
604                     }
605                 } catch (IOException ignored) {}
606             }
607         }
608 
609         @Override
610         public void close() throws IOException {
611             // BufferedInputStream#close() is not synchronized unlike most other
612             // methods. Synchronizing helps avoid race with processExited().
613             synchronized (closeLock) {
614                 super.close();
615             }
616         }
617     }
618 
619     /**
620      * A buffered output stream for a subprocess pipe file descriptor
621      * that allows the underlying file descriptor to be reclaimed when
622      * the process exits, via the processExited hook.
623      */
624     private static class ProcessPipeOutputStream extends BufferedOutputStream {
625         ProcessPipeOutputStream(int fd) {
626             super(new FileOutputStream(newFileDescriptor(fd)));
627         }
628 
629         /** Called by the process reaper thread when the process exits. */
630         synchronized void processExited() {
631             OutputStream out = this.out;
632             if (out != null) {
633                 try {
634                     out.close();
635                 } catch (IOException ignored) {
636                     // We know of no reason to get an IOException, but if
637                     // we do, there's nothing else to do but carry on.
638                 }
639                 this.out = ProcessBuilder.NullOutputStream.INSTANCE;
640             }
641         }
642     }
643 
644     // A FileInputStream that supports the deferment of the actual close
645     // operation until the last pending I/O operation on the stream has
646     // finished.  This is required on Solaris because we must close the stdin
647     // and stdout streams in the destroy method in order to reclaim the
648     // underlying file descriptors.  Doing so, however, causes any thread
649     // currently blocked in a read on one of those streams to receive an
650     // IOException("Bad file number"), which is incompatible with historical
651     // behavior.  By deferring the close we allow any pending reads to see -1
652     // (EOF) as they did before.
653     //
654     private static class DeferredCloseInputStream extends PipeInputStream {
655         DeferredCloseInputStream(FileDescriptor fd) {
656             super(fd);
657         }
658 
659         private Object lock = new Object();     // For the following fields
660         private boolean closePending = false;
661         private int useCount = 0;
662         private InputStream streamToClose;
663 
664         private void raise() {
665             synchronized (lock) {
666                 useCount++;
667             }
668         }
669 
670         private void lower() throws IOException {
671             synchronized (lock) {
672                 useCount--;
673                 if (useCount == 0 && closePending) {
674                     streamToClose.close();
675                 }
676             }
677         }
678 
679         // stc is the actual stream to be closed; it might be this object, or
680         // it might be an upstream object for which this object is downstream.
681         //
682         private void closeDeferred(InputStream stc) throws IOException {
683             synchronized (lock) {
684                 if (useCount == 0) {
685                     stc.close();
686                 } else {
687                     closePending = true;
688                     streamToClose = stc;
689                 }
690             }
691         }
692 
693         public void close() throws IOException {
694             synchronized (lock) {
695                 useCount = 0;
696                 closePending = false;
697             }
698             super.close();
699         }
700 
701         public int read() throws IOException {
702             raise();
703             try {
704                 return super.read();
705             } finally {
706                 lower();
707             }
708         }
709 
710         public int read(byte[] b) throws IOException {
711             raise();
712             try {
713                 return super.read(b);
714             } finally {
715                 lower();
716             }
717         }
718 
719         public int read(byte[] b, int off, int len) throws IOException {
720             raise();
721             try {
722                 return super.read(b, off, len);
723             } finally {
724                 lower();
725             }
726         }
727 
728         public long skip(long n) throws IOException {
729             raise();
730             try {
731                 return super.skip(n);
732             } finally {
733                 lower();
734             }
735         }
736 
737         public int available() throws IOException {
738             raise();
739             try {
740                 return super.available();
741             } finally {
742                 lower();
743             }
744         }
745     }
746 
747     /**
748      * A buffered input stream for a subprocess pipe file descriptor
749      * that allows the underlying file descriptor to be reclaimed when
750      * the process exits, via the processExited hook.
751      *
752      * This is tricky because we do not want the user-level InputStream to be
753      * closed until the user invokes close(), and we need to continue to be
754      * able to read any buffered data lingering in the OS pipe buffer.
755      *
756      * On AIX this is especially tricky, because the 'close()' system call
757      * will block if another thread is at the same time blocked in a file
758      * operation (e.g. 'read()') on the same file descriptor. We therefore
759      * combine 'ProcessPipeInputStream' approach used on Linux and Bsd
760      * with the DeferredCloseInputStream approach used on Solaris. This means
761      * that every potentially blocking operation on the file descriptor
762      * increments a counter before it is executed and decrements it once it
763      * finishes. The 'close()' operation will only be executed if there are
764      * no pending operations. Otherwise it is deferred after the last pending
765      * operation has finished.
766      *
767      */
768     private static class DeferredCloseProcessPipeInputStream
769         extends BufferedInputStream {
770 
771         private final Object closeLock = new Object();
772         private int useCount = 0;
773         private boolean closePending = false;
774 
775         DeferredCloseProcessPipeInputStream(int fd) {
776             super(new PipeInputStream(newFileDescriptor(fd)));
777         }
778 
779         private InputStream drainInputStream(InputStream in)
780                 throws IOException {
781             int n = 0;
782             int j;
783             byte[] a = null;
784             synchronized (closeLock) {
785                 if (buf == null) // asynchronous close()?
786                     return null; // discard
787                 j = in.available();
788             }
789             while (j > 0) {
790                 a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
791                 synchronized (closeLock) {
792                     if (buf == null) // asynchronous close()?
793                         return null; // discard
794                     n += in.read(a, n, j);
795                     j = in.available();
796                 }
797             }
798             return (a == null) ?
799                     ProcessBuilder.NullInputStream.INSTANCE :
800                     new ByteArrayInputStream(n == a.length ? a : Arrays.copyOf(a, n));
801         }
802 
803         /** Called by the process reaper thread when the process exits. */
804         synchronized void processExited() {
805             try {
806                 InputStream in = this.in;
807                 if (in != null) {
808                     InputStream stragglers = drainInputStream(in);
809                     in.close();
810                     this.in = stragglers;
811                 }
812             } catch (IOException ignored) { }
813         }
814 
815         private void raise() {
816             synchronized (closeLock) {
817                 useCount++;
818             }
819         }
820 
821         private void lower() throws IOException {
822             synchronized (closeLock) {
823                 useCount--;
824                 if (useCount == 0 && closePending) {
825                     closePending = false;
826                     super.close();
827                 }
828             }
829         }
830 
831         @Override
832         public int read() throws IOException {
833             raise();
834             try {
835                 return super.read();
836             } finally {
837                 lower();
838             }
839         }
840 
841         @Override
842         public int read(byte[] b) throws IOException {
843             raise();
844             try {
845                 return super.read(b);
846             } finally {
847                 lower();
848             }
849         }
850 
851         @Override
852         public int read(byte[] b, int off, int len) throws IOException {
853             raise();
854             try {
855                 return super.read(b, off, len);
856             } finally {
857                 lower();
858             }
859         }
860 
861         @Override
862         public long skip(long n) throws IOException {
863             raise();
864             try {
865                 return super.skip(n);
866             } finally {
867                 lower();
868             }
869         }
870 
871         @Override
872         public int available() throws IOException {
873             raise();
874             try {
875                 return super.available();
876             } finally {
877                 lower();
878             }
879         }
880 
881         @Override
882         public void close() throws IOException {
883             // BufferedInputStream#close() is not synchronized unlike most other
884             // methods. Synchronizing helps avoid racing with drainInputStream().
885             synchronized (closeLock) {
886                 if (useCount == 0) {
887                     super.close();
888                 }
889                 else {
890                     closePending = true;
891                 }
892             }
893         }
894     }
895 }