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.Arrays;
29import java.util.HashMap;
30import java.util.Map;
31import libcore.io.ErrnoException;
32import libcore.io.IoUtils;
33import libcore.io.Libcore;
34import libcore.util.MutableInt;
35import static libcore.io.OsConstants.*;
36
37/**
38 * Manages child processes.
39 */
40final class ProcessManager {
41    /**
42     * Map from pid to Process. We keep weak references to the Process objects
43     * and clean up the entries when no more external references are left. The
44     * process objects themselves don't require much memory, but file
45     * descriptors (associated with stdin/stdout/stderr in this case) can be
46     * a scarce resource.
47     */
48    private final Map<Integer, ProcessReference> processReferences
49            = new HashMap<Integer, ProcessReference>();
50
51    /** Keeps track of garbage-collected Processes. */
52    private final ProcessReferenceQueue referenceQueue = new ProcessReferenceQueue();
53
54    private ProcessManager() {
55        // Spawn a thread to listen for signals from child processes.
56        Thread reaperThread = new Thread(ProcessManager.class.getName()) {
57            @Override public void run() {
58                watchChildren();
59            }
60        };
61        reaperThread.setDaemon(true);
62        reaperThread.start();
63    }
64
65    /**
66     * Cleans up after garbage collected processes. Requires the lock on the
67     * map.
68     */
69    private void cleanUp() {
70        ProcessReference reference;
71        while ((reference = referenceQueue.poll()) != null) {
72            synchronized (processReferences) {
73                processReferences.remove(reference.processId);
74            }
75        }
76    }
77
78    /**
79     * Loops indefinitely and calls ProcessManager.onExit() when children exit.
80     */
81    private void watchChildren() {
82        MutableInt status = new MutableInt(-1);
83        while (true) {
84            try {
85                // Wait for children in our process group.
86                int pid = Libcore.os.waitpid(0, status, 0);
87
88                // Work out what onExit wants to hear.
89                int exitValue;
90                if (WIFEXITED(status.value)) {
91                    exitValue = WEXITSTATUS(status.value);
92                } else if (WIFSIGNALED(status.value)) {
93                    exitValue = WTERMSIG(status.value);
94                } else if (WIFSTOPPED(status.value)) {
95                    exitValue = WSTOPSIG(status.value);
96                } else {
97                    throw new AssertionError("unexpected status from waitpid: " + status.value);
98                }
99
100                onExit(pid, exitValue);
101            } catch (ErrnoException errnoException) {
102                if (errnoException.errno == ECHILD) {
103                    // Expected errno: there are no children to wait for.
104                    // onExit will sleep until it is informed of another child coming to life.
105                    waitForMoreChildren();
106                    continue;
107                } else {
108                    throw new AssertionError(errnoException);
109                }
110            }
111        }
112    }
113
114    /**
115     * Called by {@link #watchChildren()} when a child process exits.
116     *
117     * @param pid ID of process that exited
118     * @param exitValue value the process returned upon exit
119     */
120    private void onExit(int pid, int exitValue) {
121        ProcessReference processReference = null;
122        synchronized (processReferences) {
123            cleanUp();
124            processReference = processReferences.remove(pid);
125        }
126        if (processReference != null) {
127            ProcessImpl process = processReference.get();
128            if (process != null) {
129                process.setExitValue(exitValue);
130            }
131        }
132    }
133
134    private void waitForMoreChildren() {
135        synchronized (processReferences) {
136            if (processReferences.isEmpty()) {
137                // There are no eligible children; wait for one to be added.
138                // This wait will return because of the notifyAll call in exec.
139                try {
140                    processReferences.wait();
141                } catch (InterruptedException ex) {
142                    // This should never happen.
143                    throw new AssertionError("unexpected interrupt");
144                }
145            } else {
146                /*
147                 * A new child was spawned just before we entered
148                 * the synchronized block. We can just fall through
149                 * without doing anything special and land back in
150                 * the native waitpid().
151                 */
152            }
153        }
154    }
155
156    /**
157     * Executes a native process. Fills in in, out, and err and returns the
158     * new process ID upon success.
159     */
160    private static native int exec(String[] command, String[] environment,
161            String workingDirectory, FileDescriptor in, FileDescriptor out,
162            FileDescriptor err, boolean redirectErrorStream) throws IOException;
163
164    /**
165     * Executes a process and returns an object representing it.
166     */
167    public Process exec(String[] taintedCommand, String[] taintedEnvironment, File workingDirectory,
168            boolean redirectErrorStream) throws IOException {
169        // Make sure we throw the same exceptions as the RI.
170        if (taintedCommand == null) {
171            throw new NullPointerException("taintedCommand == null");
172        }
173        if (taintedCommand.length == 0) {
174            throw new IndexOutOfBoundsException("taintedCommand.length == 0");
175        }
176
177        // Handle security and safety by copying mutable inputs and checking them.
178        String[] command = taintedCommand.clone();
179        String[] environment = taintedEnvironment != null ? taintedEnvironment.clone() : null;
180
181        // Check we're not passing null Strings to the native exec.
182        for (int i = 0; i < command.length; i++) {
183            if (command[i] == null) {
184                throw new NullPointerException("taintedCommand[" + i + "] == null");
185            }
186        }
187        // The environment is allowed to be null or empty, but no element may be null.
188        if (environment != null) {
189            for (int i = 0; i < environment.length; i++) {
190                if (environment[i] == null) {
191                    throw new NullPointerException("taintedEnvironment[" + i + "] == null");
192                }
193            }
194        }
195
196        FileDescriptor in = new FileDescriptor();
197        FileDescriptor out = new FileDescriptor();
198        FileDescriptor err = new FileDescriptor();
199
200        String workingPath = (workingDirectory == null)
201                ? null
202                : workingDirectory.getPath();
203
204        // Ensure onExit() doesn't access the process map before we add our
205        // entry.
206        synchronized (processReferences) {
207            int pid;
208            try {
209                pid = exec(command, environment, workingPath, in, out, err, redirectErrorStream);
210            } catch (IOException e) {
211                IOException wrapper = new IOException("Error running exec()."
212                        + " Command: " + Arrays.toString(command)
213                        + " Working Directory: " + workingDirectory
214                        + " Environment: " + Arrays.toString(environment));
215                wrapper.initCause(e);
216                throw wrapper;
217            }
218            ProcessImpl process = new ProcessImpl(pid, in, out, err);
219            ProcessReference processReference = new ProcessReference(process, referenceQueue);
220            processReferences.put(pid, processReference);
221
222            /*
223             * This will wake up the child monitor thread in case there
224             * weren't previously any children to wait on.
225             */
226            processReferences.notifyAll();
227
228            return process;
229        }
230    }
231
232    static class ProcessImpl extends Process {
233        private final int pid;
234
235        private final InputStream errorStream;
236
237        /** Reads output from process. */
238        private final InputStream inputStream;
239
240        /** Sends output to process. */
241        private final OutputStream outputStream;
242
243        /** The process's exit value. */
244        private Integer exitValue = null;
245        private final Object exitValueMutex = new Object();
246
247        ProcessImpl(int pid, FileDescriptor in, FileDescriptor out, FileDescriptor err) {
248            this.pid = pid;
249
250            this.errorStream = new ProcessInputStream(err);
251            this.inputStream = new ProcessInputStream(in);
252            this.outputStream = new ProcessOutputStream(out);
253        }
254
255        public void destroy() {
256            // If the process hasn't already exited, send it SIGKILL.
257            synchronized (exitValueMutex) {
258                if (exitValue == null) {
259                    try {
260                        Libcore.os.kill(pid, SIGKILL);
261                    } catch (ErrnoException e) {
262                        System.logI("Failed to destroy process " + pid, e);
263                    }
264                }
265            }
266            // Close any open streams.
267            IoUtils.closeQuietly(inputStream);
268            IoUtils.closeQuietly(errorStream);
269            IoUtils.closeQuietly(outputStream);
270        }
271
272        public int exitValue() {
273            synchronized (exitValueMutex) {
274                if (exitValue == null) {
275                    throw new IllegalThreadStateException("Process has not yet terminated: " + pid);
276                }
277                return exitValue;
278            }
279        }
280
281        public InputStream getErrorStream() {
282            return this.errorStream;
283        }
284
285        public InputStream getInputStream() {
286            return this.inputStream;
287        }
288
289        public OutputStream getOutputStream() {
290            return this.outputStream;
291        }
292
293        public int waitFor() throws InterruptedException {
294            synchronized (exitValueMutex) {
295                while (exitValue == null) {
296                    exitValueMutex.wait();
297                }
298                return exitValue;
299            }
300        }
301
302        void setExitValue(int exitValue) {
303            synchronized (exitValueMutex) {
304                this.exitValue = exitValue;
305                exitValueMutex.notifyAll();
306            }
307        }
308
309        @Override
310        public String toString() {
311            return "Process[pid=" + pid + "]";
312        }
313    }
314
315    static class ProcessReference extends WeakReference<ProcessImpl> {
316
317        final int processId;
318
319        public ProcessReference(ProcessImpl referent, ProcessReferenceQueue referenceQueue) {
320            super(referent, referenceQueue);
321            this.processId = referent.pid;
322        }
323    }
324
325    static class ProcessReferenceQueue extends ReferenceQueue<ProcessImpl> {
326
327        @Override
328        public ProcessReference poll() {
329            // Why couldn't they get the generics right on ReferenceQueue? :(
330            Object reference = super.poll();
331            return (ProcessReference) reference;
332        }
333    }
334
335    private static final ProcessManager instance = new ProcessManager();
336
337    /** Gets the process manager. */
338    public static ProcessManager getInstance() {
339        return instance;
340    }
341
342    /** Automatically closes fd when collected. */
343    private static class ProcessInputStream extends FileInputStream {
344
345        private FileDescriptor fd;
346
347        private ProcessInputStream(FileDescriptor fd) {
348            super(fd);
349            this.fd = fd;
350        }
351
352        @Override
353        public void close() throws IOException {
354            try {
355                super.close();
356            } finally {
357                synchronized (this) {
358                    try {
359                        IoUtils.close(fd);
360                    } finally {
361                        fd = null;
362                    }
363                }
364            }
365        }
366    }
367
368    /** Automatically closes fd when collected. */
369    private static class ProcessOutputStream extends FileOutputStream {
370
371        private FileDescriptor fd;
372
373        private ProcessOutputStream(FileDescriptor fd) {
374            super(fd);
375            this.fd = fd;
376        }
377
378        @Override
379        public void close() throws IOException {
380            try {
381                super.close();
382            } finally {
383                synchronized (this) {
384                    try {
385                        IoUtils.close(fd);
386                    } finally {
387                        fd = null;
388                    }
389                }
390            }
391        }
392    }
393}
394