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 
17 package java.lang;
18 
19 import android.system.ErrnoException;
20 import android.util.MutableInt;
21 import java.io.File;
22 import java.io.FileDescriptor;
23 import java.io.FileInputStream;
24 import java.io.FileOutputStream;
25 import java.io.InputStream;
26 import java.io.IOException;
27 import java.io.OutputStream;
28 import java.lang.ref.ReferenceQueue;
29 import java.lang.ref.WeakReference;
30 import java.util.Arrays;
31 import java.util.HashMap;
32 import java.util.Map;
33 import libcore.io.IoUtils;
34 import libcore.io.Libcore;
35 import static android.system.OsConstants.*;
36 
37 /**
38  * Manages child processes.
39  */
40 final 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 
ProcessManager()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      */
cleanUp()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      */
watchChildren()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      */
onExit(int pid, int exitValue)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 
waitForMoreChildren()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      */
exec(String[] command, String[] environment, String workingDirectory, FileDescriptor in, FileDescriptor out, FileDescriptor err, boolean redirectErrorStream)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      */
exec(String[] taintedCommand, String[] taintedEnvironment, File workingDirectory, boolean redirectErrorStream)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 
ProcessImpl(int pid, FileDescriptor in, FileDescriptor out, FileDescriptor err)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 
destroy()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 
exitValue()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 
getErrorStream()281         public InputStream getErrorStream() {
282             return this.errorStream;
283         }
284 
getInputStream()285         public InputStream getInputStream() {
286             return this.inputStream;
287         }
288 
getOutputStream()289         public OutputStream getOutputStream() {
290             return this.outputStream;
291         }
292 
waitFor()293         public int waitFor() throws InterruptedException {
294             synchronized (exitValueMutex) {
295                 while (exitValue == null) {
296                     exitValueMutex.wait();
297                 }
298                 return exitValue;
299             }
300         }
301 
setExitValue(int exitValue)302         void setExitValue(int exitValue) {
303             synchronized (exitValueMutex) {
304                 this.exitValue = exitValue;
305                 exitValueMutex.notifyAll();
306             }
307         }
308 
309         @Override
toString()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 
ProcessReference(ProcessImpl referent, ProcessReferenceQueue referenceQueue)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
poll()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. */
getInstance()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 
ProcessInputStream(FileDescriptor fd)347         private ProcessInputStream(FileDescriptor fd) {
348             super(fd);
349             this.fd = fd;
350         }
351 
352         @Override
close()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 
ProcessOutputStream(FileDescriptor fd)373         private ProcessOutputStream(FileDescriptor fd) {
374             super(fd);
375             this.fd = fd;
376         }
377 
378         @Override
close()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