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