1/*
2 * Copyright (C) 2007 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package java.lang;
18
19import java.io.File;
20import java.io.FileDescriptor;
21import java.io.FileInputStream;
22import java.io.FileOutputStream;
23import java.io.IOException;
24import java.io.InputStream;
25import java.io.OutputStream;
26import java.lang.ref.ReferenceQueue;
27import java.lang.ref.WeakReference;
28import java.util.HashMap;
29import java.util.Map;
30import java.util.Arrays;
31import java.util.logging.Logger;
32import java.util.logging.Level;
33
34/**
35 * Manages child processes.
36 *
37 * <p>Harmony's native implementation (for comparison purposes):
38 * http://tinyurl.com/3ytwuq
39 */
40final class ProcessManager {
41
42    /**
43     * constant communicated from native code indicating that a
44     * child died, but it was unable to determine the status
45     */
46    private static final int WAIT_STATUS_UNKNOWN = -1;
47
48    /**
49     * constant communicated from native code indicating that there
50     * are currently no children to wait for
51     */
52    private static final int WAIT_STATUS_NO_CHILDREN = -2;
53
54    /**
55     * constant communicated from native code indicating that a wait()
56     * call returned -1 and set an undocumented (and hence unexpected) errno
57     */
58    private static final int WAIT_STATUS_STRANGE_ERRNO = -3;
59
60    /**
61     * Initializes native static state.
62     */
63    static native void staticInitialize();
64    static {
65        staticInitialize();
66    }
67
68    /**
69     * Map from pid to Process. We keep weak references to the Process objects
70     * and clean up the entries when no more external references are left. The
71     * process objects themselves don't require much memory, but file
72     * descriptors (associated with stdin/out/err in this case) can be
73     * a scarce resource.
74     */
75    private final Map<Integer, ProcessReference> processReferences
76            = new HashMap<Integer, ProcessReference>();
77
78    /** Keeps track of garbage-collected Processes. */
79    private final ProcessReferenceQueue referenceQueue
80            = new ProcessReferenceQueue();
81
82    private ProcessManager() {
83        // Spawn a thread to listen for signals from child processes.
84        Thread processThread = new Thread(ProcessManager.class.getName()) {
85            @Override
86            public void run() {
87                watchChildren();
88            }
89        };
90        processThread.setDaemon(true);
91        processThread.start();
92    }
93
94    /**
95     * Kills the process with the given ID.
96     *
97     * @parm pid ID of process to kill
98     */
99    private static native void kill(int pid) throws IOException;
100
101    /**
102     * Cleans up after garbage collected processes. Requires the lock on the
103     * map.
104     */
105    void cleanUp() {
106        ProcessReference reference;
107        while ((reference = referenceQueue.poll()) != null) {
108            synchronized (processReferences) {
109                processReferences.remove(reference.processId);
110            }
111        }
112    }
113
114    /**
115     * Listens for signals from processes and calls back to
116     * {@link #onExit(int,int)}.
117     */
118    native void watchChildren();
119
120    /**
121     * Called by {@link #watchChildren()} when a child process exits.
122     *
123     * @param pid ID of process that exited
124     * @param exitValue value the process returned upon exit
125     */
126    void onExit(int pid, int exitValue) {
127        ProcessReference processReference = null;
128
129        synchronized (processReferences) {
130            cleanUp();
131            if (pid >= 0) {
132                processReference = processReferences.remove(pid);
133            } else if (exitValue == WAIT_STATUS_NO_CHILDREN) {
134                if (processReferences.isEmpty()) {
135                    /*
136                     * There are no eligible children; wait for one to be
137                     * added. The wait() will return due to the
138                     * notifyAll() call below.
139                     */
140                    try {
141                        processReferences.wait();
142                    } catch (InterruptedException ex) {
143                        // This should never happen.
144                        throw new AssertionError("unexpected interrupt");
145                    }
146                } else {
147                    /*
148                     * A new child was spawned just before we entered
149                     * the synchronized block. We can just fall through
150                     * without doing anything special and land back in
151                     * the native wait().
152                     */
153                }
154            } else {
155                // Something weird is happening; abort!
156                throw new AssertionError("unexpected wait() behavior");
157            }
158        }
159
160        if (processReference != null) {
161            ProcessImpl process = processReference.get();
162            if (process != null) {
163                process.setExitValue(exitValue);
164            }
165        }
166    }
167
168    /**
169     * Executes a native process. Fills in in, out, and err and returns the
170     * new process ID upon success.
171     */
172    static native int exec(String[] commands, String[] environment,
173            String workingDirectory, FileDescriptor in, FileDescriptor out,
174            FileDescriptor err) throws IOException;
175
176    /**
177     * Executes a process and returns an object representing it.
178     */
179    Process exec(String[] commands, String[] environment,
180            File workingDirectory) throws IOException {
181        FileDescriptor in = new FileDescriptor();
182        FileDescriptor out = new FileDescriptor();
183        FileDescriptor err = new FileDescriptor();
184
185        String workingPath = (workingDirectory == null)
186                ? null
187                : workingDirectory.getPath();
188
189        // Ensure onExit() doesn't access the process map before we add our
190        // entry.
191        synchronized (processReferences) {
192            int pid;
193            try {
194                pid = exec(commands, environment, workingPath, in, out, err);
195            } catch (IOException e) {
196                IOException wrapper = new IOException("Error running exec()."
197                        + " Commands: " + Arrays.toString(commands)
198                        + " Working Directory: " + workingDirectory
199                        + " Environment: " + Arrays.toString(environment));
200                wrapper.initCause(e);
201                throw wrapper;
202            }
203            ProcessImpl process = new ProcessImpl(pid, in, out, err);
204            ProcessReference processReference
205                    = new ProcessReference(process, referenceQueue);
206            processReferences.put(pid, processReference);
207
208            /*
209             * This will wake up the child monitor thread in case there
210             * weren't previously any children to wait on.
211             */
212            processReferences.notifyAll();
213
214            return process;
215        }
216    }
217
218    static class ProcessImpl extends Process {
219
220        /** Process ID. */
221        final int id;
222
223        final InputStream errorStream;
224
225        /** Reads output from process. */
226        final InputStream inputStream;
227
228        /** Sends output to process. */
229        final OutputStream outputStream;
230
231        /** The process's exit value. */
232        Integer exitValue = null;
233        final Object exitValueMutex = new Object();
234
235        ProcessImpl(int id, FileDescriptor in, FileDescriptor out,
236                FileDescriptor err) {
237            this.id = id;
238
239            this.errorStream = new ProcessInputStream(err);
240            this.inputStream = new ProcessInputStream(in);
241            this.outputStream = new ProcessOutputStream(out);
242        }
243
244        public void destroy() {
245            try {
246                kill(this.id);
247            } catch (IOException e) {
248                Logger.getLogger(Runtime.class.getName()).log(Level.FINE,
249                        "Failed to destroy process " + id + ".", e);
250            }
251        }
252
253        public int exitValue() {
254            synchronized (exitValueMutex) {
255                if (exitValue == null) {
256                    throw new IllegalThreadStateException(
257                            "Process has not yet terminated.");
258                }
259
260                return exitValue;
261            }
262        }
263
264        public InputStream getErrorStream() {
265            return this.errorStream;
266        }
267
268        public InputStream getInputStream() {
269            return this.inputStream;
270        }
271
272        public OutputStream getOutputStream() {
273            return this.outputStream;
274        }
275
276        public int waitFor() throws InterruptedException {
277            synchronized (exitValueMutex) {
278                while (exitValue == null) {
279                    exitValueMutex.wait();
280                }
281                return exitValue;
282            }
283        }
284
285        void setExitValue(int exitValue) {
286            synchronized (exitValueMutex) {
287                this.exitValue = exitValue;
288                exitValueMutex.notifyAll();
289            }
290        }
291
292        @Override
293        public String toString() {
294            return "Process[id=" + id + "]";
295        }
296    }
297
298    static class ProcessReference extends WeakReference<ProcessImpl> {
299
300        final int processId;
301
302        public ProcessReference(ProcessImpl referent,
303                ProcessReferenceQueue referenceQueue) {
304            super(referent, referenceQueue);
305            this.processId = referent.id;
306        }
307    }
308
309    static class ProcessReferenceQueue extends ReferenceQueue<ProcessImpl> {
310
311        @Override
312        public ProcessReference poll() {
313            // Why couldn't they get the generics right on ReferenceQueue? :(
314            Object reference = super.poll();
315            return (ProcessReference) reference;
316        }
317    }
318
319    static final ProcessManager instance = new ProcessManager();
320
321    /** Gets the process manager. */
322    static ProcessManager getInstance() {
323        return instance;
324    }
325
326    /** Automatically closes fd when collected. */
327    private static class ProcessInputStream extends FileInputStream {
328
329        private FileDescriptor fd;
330
331        private ProcessInputStream(FileDescriptor fd) {
332            super(fd);
333            this.fd = fd;
334        }
335
336        @Override
337        public void close() throws IOException {
338            try {
339                super.close();
340            } finally {
341                synchronized (this) {
342                    if (fd != null && fd.valid()) {
343                        try {
344                            ProcessManager.close(fd);
345                        } finally {
346                            fd = null;
347                        }
348                    }
349                }
350            }
351        }
352    }
353
354    /** Automatically closes fd when collected. */
355    private static class ProcessOutputStream extends FileOutputStream {
356
357        private FileDescriptor fd;
358
359        private ProcessOutputStream(FileDescriptor fd) {
360            super(fd);
361            this.fd = fd;
362        }
363
364        @Override
365        public void close() throws IOException {
366            try {
367                super.close();
368            } finally {
369                synchronized (this) {
370                    if (fd != null && fd.valid()) {
371                        try {
372                            ProcessManager.close(fd);
373                        } finally {
374                            fd = null;
375                        }
376                    }
377                }
378            }
379        }
380    }
381
382    /** Closes the given file descriptor. */
383    private static native void close(FileDescriptor fd) throws IOException;
384}
385