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