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