1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 * Copyright (c) 1995, 2010, Oracle and/or its affiliates. All rights reserved.
4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5 *
6 * This code is free software; you can redistribute it and/or modify it
7 * under the terms of the GNU General Public License version 2 only, as
8 * published by the Free Software Foundation.  Oracle designates this
9 * particular file as subject to the "Classpath" exception as provided
10 * by Oracle in the LICENSE file that accompanied this code.
11 *
12 * This code is distributed in the hope that it will be useful, but WITHOUT
13 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15 * version 2 for more details (a copy is included in the LICENSE file that
16 * accompanied this code).
17 *
18 * You should have received a copy of the GNU General Public License version
19 * 2 along with this work; if not, write to the Free Software Foundation,
20 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21 *
22 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23 * or visit www.oracle.com if you need additional information or have any
24 * questions.
25 */
26
27package java.lang;
28
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.concurrent.Executors;
40import java.util.concurrent.Executor;
41import java.util.concurrent.ThreadFactory;
42import java.security.AccessController;
43import static java.security.AccessController.doPrivileged;
44import java.security.PrivilegedAction;
45import java.security.PrivilegedActionException;
46import java.security.PrivilegedExceptionAction;
47
48/**
49 * java.lang.Process subclass in the UNIX environment.
50 *
51 * @author Mario Wolczko and Ross Knippel.
52 * @author Konstantin Kladko (ported to Linux)
53 * @author Martin Buchholz
54 */
55final class UNIXProcess extends Process {
56    private final int pid;
57    private int exitcode;
58    private boolean hasExited;
59
60    private /* final */ OutputStream stdin;
61    private /* final */ InputStream  stdout;
62    private /* final */ InputStream  stderr;
63
64    /* this is for the reaping thread */
65    private native int waitForProcessExit(int pid);
66
67    /**
68     * Create a process using fork(2) and exec(2).
69     *
70     * @param fds an array of three file descriptors.
71     *        Indexes 0, 1, and 2 correspond to standard input,
72     *        standard output and standard error, respectively.  On
73     *        input, a value of -1 means to create a pipe to connect
74     *        child and parent processes.  On output, a value which
75     *        is not -1 is the parent pipe fd corresponding to the
76     *        pipe which has been created.  An element of this array
77     *        is -1 on input if and only if it is <em>not</em> -1 on
78     *        output.
79     * @return the pid of the subprocess
80     */
81    private native int forkAndExec(byte[] prog,
82                                   byte[] argBlock, int argc,
83                                   byte[] envBlock, int envc,
84                                   byte[] dir,
85                                   int[] fds,
86                                   boolean redirectErrorStream)
87        throws IOException;
88
89    /**
90     * The thread factory used to create "process reaper" daemon threads.
91     */
92    private static class ProcessReaperThreadFactory implements ThreadFactory {
93        private final static ThreadGroup group = getRootThreadGroup();
94
95        private static ThreadGroup getRootThreadGroup() {
96            return doPrivileged(new PrivilegedAction<ThreadGroup> () {
97                public ThreadGroup run() {
98                    ThreadGroup root = Thread.currentThread().getThreadGroup();
99                    while (root.getParent() != null)
100                        root = root.getParent();
101                    return root;
102                }});
103        }
104
105        public Thread newThread(Runnable grimReaper) {
106            // Our thread stack requirement is quite modest.
107            Thread t = new Thread(group, grimReaper, "process reaper", 32768);
108            t.setDaemon(true);
109            // A small attempt (probably futile) to avoid priority inversion
110            t.setPriority(Thread.MAX_PRIORITY);
111            return t;
112        }
113    }
114
115    /**
116     * The thread pool of "process reaper" daemon threads.
117     */
118    private static final Executor processReaperExecutor =
119        doPrivileged(new PrivilegedAction<Executor>() {
120            public Executor run() {
121                return Executors.newCachedThreadPool
122                    (new ProcessReaperThreadFactory());
123            }});
124
125    UNIXProcess(final byte[] prog,
126                final byte[] argBlock, final int argc,
127                final byte[] envBlock, final int envc,
128                final byte[] dir,
129                final int[] fds,
130                final boolean redirectErrorStream)
131            throws IOException {
132
133        pid = forkAndExec(prog,
134                          argBlock, argc,
135                          envBlock, envc,
136                          dir,
137                          fds,
138                          redirectErrorStream);
139
140        try {
141            doPrivileged(new PrivilegedExceptionAction<Void>() {
142                public Void run() throws IOException {
143                    initStreams(fds);
144                    return null;
145                }});
146        } catch (PrivilegedActionException ex) {
147            throw (IOException) ex.getException();
148        }
149    }
150
151    static FileDescriptor newFileDescriptor(int fd) {
152        FileDescriptor fileDescriptor = new FileDescriptor();
153        fileDescriptor.setInt$(fd);
154        return fileDescriptor;
155    }
156
157    void initStreams(int[] fds) throws IOException {
158        stdin = (fds[0] == -1) ?
159            ProcessBuilder.NullOutputStream.INSTANCE :
160            new ProcessPipeOutputStream(fds[0]);
161
162        stdout = (fds[1] == -1) ?
163            ProcessBuilder.NullInputStream.INSTANCE :
164            new ProcessPipeInputStream(fds[1]);
165
166        stderr = (fds[2] == -1) ?
167            ProcessBuilder.NullInputStream.INSTANCE :
168            new ProcessPipeInputStream(fds[2]);
169
170        processReaperExecutor.execute(new Runnable() {
171            public void run() {
172                int exitcode = waitForProcessExit(pid);
173                UNIXProcess.this.processExited(exitcode);
174            }});
175    }
176
177    void processExited(int exitcode) {
178        synchronized (this) {
179            this.exitcode = exitcode;
180            hasExited = true;
181            notifyAll();
182        }
183
184        if (stdout instanceof ProcessPipeInputStream)
185            ((ProcessPipeInputStream) stdout).processExited();
186
187        if (stderr instanceof ProcessPipeInputStream)
188            ((ProcessPipeInputStream) stderr).processExited();
189
190        if (stdin instanceof ProcessPipeOutputStream)
191            ((ProcessPipeOutputStream) stdin).processExited();
192    }
193
194    public OutputStream getOutputStream() {
195        return stdin;
196    }
197
198    public InputStream getInputStream() {
199        return stdout;
200    }
201
202    public InputStream getErrorStream() {
203        return stderr;
204    }
205
206    public synchronized int waitFor() throws InterruptedException {
207        while (!hasExited) {
208            wait();
209        }
210        return exitcode;
211    }
212
213    public synchronized int exitValue() {
214        if (!hasExited) {
215            throw new IllegalThreadStateException("process hasn't exited");
216        }
217        return exitcode;
218    }
219
220    private static native void destroyProcess(int pid);
221    public void destroy() {
222        // There is a risk that pid will be recycled, causing us to
223        // kill the wrong process!  So we only terminate processes
224        // that appear to still be running.  Even with this check,
225        // there is an unavoidable race condition here, but the window
226        // is very small, and OSes try hard to not recycle pids too
227        // soon, so this is quite safe.
228        synchronized (this) {
229            if (!hasExited)
230                destroyProcess(pid);
231        }
232        try { stdin.close();  } catch (IOException ignored) {}
233        try { stdout.close(); } catch (IOException ignored) {}
234        try { stderr.close(); } catch (IOException ignored) {}
235    }
236
237    @Override
238    public String toString() {
239        StringBuilder sb = new StringBuilder("Process[pid=");
240        sb.append(pid);
241        if (hasExited) {
242            sb.append(" ,hasExited=true, exitcode=");
243            sb.append(exitcode);
244            sb.append("]");
245        } else {
246            sb.append(", hasExited=false]");
247        }
248
249        return sb.toString();
250    }
251
252    /* This routine initializes JNI field offsets for the class */
253    private static native void initIDs();
254
255    static {
256        initIDs();
257    }
258
259    /**
260     * A buffered input stream for a subprocess pipe file descriptor
261     * that allows the underlying file descriptor to be reclaimed when
262     * the process exits, via the processExited hook.
263     *
264     * This is tricky because we do not want the user-level InputStream to be
265     * closed until the user invokes close(), and we need to continue to be
266     * able to read any buffered data lingering in the OS pipe buffer.
267     */
268    static class ProcessPipeInputStream extends BufferedInputStream {
269        ProcessPipeInputStream(int fd) {
270            super(new FileInputStream(newFileDescriptor(fd), true /* isFdOwner */));
271        }
272
273        private static byte[] drainInputStream(InputStream in)
274                throws IOException {
275            if (in == null) return null;
276            int n = 0;
277            int j;
278            byte[] a = null;
279            while ((j = in.available()) > 0) {
280                a = (a == null) ? new byte[j] : Arrays.copyOf(a, n + j);
281                n += in.read(a, n, j);
282            }
283            return (a == null || n == a.length) ? a : Arrays.copyOf(a, n);
284        }
285
286        /** Called by the process reaper thread when the process exits. */
287        synchronized void processExited() {
288            // Most BufferedInputStream methods are synchronized, but close()
289            // is not, and so we have to handle concurrent racing close().
290            try {
291                InputStream in = this.in;
292                if (in != null) {
293                    byte[] stragglers = drainInputStream(in);
294                    in.close();
295                    this.in = (stragglers == null) ?
296                        ProcessBuilder.NullInputStream.INSTANCE :
297                        new ByteArrayInputStream(stragglers);
298                    if (buf == null) // asynchronous close()?
299                        this.in = null;
300                }
301            } catch (IOException ignored) {
302                // probably an asynchronous close().
303            }
304        }
305    }
306
307    /**
308     * A buffered output stream for a subprocess pipe file descriptor
309     * that allows the underlying file descriptor to be reclaimed when
310     * the process exits, via the processExited hook.
311     */
312    static class ProcessPipeOutputStream extends BufferedOutputStream {
313        ProcessPipeOutputStream(int fd) {
314            super(new FileOutputStream(newFileDescriptor(fd), true /* isFdOwner */));
315        }
316
317        /** Called by the process reaper thread when the process exits. */
318        synchronized void processExited() {
319            OutputStream out = this.out;
320            if (out != null) {
321                try {
322                    out.close();
323                } catch (IOException ignored) {
324                    // We know of no reason to get an IOException, but if
325                    // we do, there's nothing else to do but carry on.
326                }
327                this.out = ProcessBuilder.NullOutputStream.INSTANCE;
328            }
329        }
330    }
331}
332