| /* |
| * Copyright (C) 2007 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package java.lang; |
| |
| import java.io.File; |
| import java.io.FileDescriptor; |
| import java.io.FileInputStream; |
| import java.io.FileOutputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.OutputStream; |
| import java.lang.ref.ReferenceQueue; |
| import java.lang.ref.WeakReference; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Arrays; |
| import java.util.logging.Logger; |
| import java.util.logging.Level; |
| |
| /** |
| * Manages child processes. |
| * |
| * <p>Harmony's native implementation (for comparison purposes): |
| * http://tinyurl.com/3ytwuq |
| */ |
| final class ProcessManager { |
| |
| /** |
| * constant communicated from native code indicating that a |
| * child died, but it was unable to determine the status |
| */ |
| private static final int WAIT_STATUS_UNKNOWN = -1; |
| |
| /** |
| * constant communicated from native code indicating that there |
| * are currently no children to wait for |
| */ |
| private static final int WAIT_STATUS_NO_CHILDREN = -2; |
| |
| /** |
| * constant communicated from native code indicating that a wait() |
| * call returned -1 and set an undocumented (and hence unexpected) errno |
| */ |
| private static final int WAIT_STATUS_STRANGE_ERRNO = -3; |
| |
| /** |
| * Initializes native static state. |
| */ |
| static native void staticInitialize(); |
| static { |
| staticInitialize(); |
| } |
| |
| /** |
| * Map from pid to Process. We keep weak references to the Process objects |
| * and clean up the entries when no more external references are left. The |
| * process objects themselves don't require much memory, but file |
| * descriptors (associated with stdin/out/err in this case) can be |
| * a scarce resource. |
| */ |
| private final Map<Integer, ProcessReference> processReferences |
| = new HashMap<Integer, ProcessReference>(); |
| |
| /** Keeps track of garbage-collected Processes. */ |
| private final ProcessReferenceQueue referenceQueue |
| = new ProcessReferenceQueue(); |
| |
| private ProcessManager() { |
| // Spawn a thread to listen for signals from child processes. |
| Thread processThread = new Thread(ProcessManager.class.getName()) { |
| @Override |
| public void run() { |
| watchChildren(); |
| } |
| }; |
| processThread.setDaemon(true); |
| processThread.start(); |
| } |
| |
| /** |
| * Kills the process with the given ID. |
| * |
| * @parm pid ID of process to kill |
| */ |
| private static native void kill(int pid) throws IOException; |
| |
| /** |
| * Cleans up after garbage collected processes. Requires the lock on the |
| * map. |
| */ |
| void cleanUp() { |
| ProcessReference reference; |
| while ((reference = referenceQueue.poll()) != null) { |
| synchronized (processReferences) { |
| processReferences.remove(reference.processId); |
| } |
| } |
| } |
| |
| /** |
| * Listens for signals from processes and calls back to |
| * {@link #onExit(int,int)}. |
| */ |
| native void watchChildren(); |
| |
| /** |
| * Called by {@link #watchChildren()} when a child process exits. |
| * |
| * @param pid ID of process that exited |
| * @param exitValue value the process returned upon exit |
| */ |
| void onExit(int pid, int exitValue) { |
| ProcessReference processReference = null; |
| |
| synchronized (processReferences) { |
| cleanUp(); |
| if (pid >= 0) { |
| processReference = processReferences.remove(pid); |
| } else if (exitValue == WAIT_STATUS_NO_CHILDREN) { |
| if (processReferences.isEmpty()) { |
| /* |
| * There are no eligible children; wait for one to be |
| * added. The wait() will return due to the |
| * notifyAll() call below. |
| */ |
| try { |
| processReferences.wait(); |
| } catch (InterruptedException ex) { |
| // This should never happen. |
| throw new AssertionError("unexpected interrupt"); |
| } |
| } else { |
| /* |
| * A new child was spawned just before we entered |
| * the synchronized block. We can just fall through |
| * without doing anything special and land back in |
| * the native wait(). |
| */ |
| } |
| } else { |
| // Something weird is happening; abort! |
| throw new AssertionError("unexpected wait() behavior"); |
| } |
| } |
| |
| if (processReference != null) { |
| ProcessImpl process = processReference.get(); |
| if (process != null) { |
| process.setExitValue(exitValue); |
| } |
| } |
| } |
| |
| /** |
| * Executes a native process. Fills in in, out, and err and returns the |
| * new process ID upon success. |
| */ |
| static native int exec(String[] command, String[] environment, |
| String workingDirectory, FileDescriptor in, FileDescriptor out, |
| FileDescriptor err, boolean redirectErrorStream) throws IOException; |
| |
| /** |
| * Executes a process and returns an object representing it. |
| */ |
| Process exec(String[] taintedCommand, String[] taintedEnvironment, File workingDirectory, |
| boolean redirectErrorStream) throws IOException { |
| // Make sure we throw the same exceptions as the RI. |
| if (taintedCommand == null) { |
| throw new NullPointerException(); |
| } |
| if (taintedCommand.length == 0) { |
| throw new IndexOutOfBoundsException(); |
| } |
| |
| // Handle security and safety by copying mutable inputs and checking them. |
| String[] command = taintedCommand.clone(); |
| String[] environment = taintedEnvironment != null ? taintedEnvironment.clone() : null; |
| SecurityManager securityManager = System.getSecurityManager(); |
| if (securityManager != null) { |
| securityManager.checkExec(command[0]); |
| } |
| // Check we're not passing null Strings to the native exec. |
| for (String arg : command) { |
| if (arg == null) { |
| throw new NullPointerException(); |
| } |
| } |
| // The environment is allowed to be null or empty, but no element may be null. |
| if (environment != null) { |
| for (String env : environment) { |
| if (env == null) { |
| throw new NullPointerException(); |
| } |
| } |
| } |
| |
| FileDescriptor in = new FileDescriptor(); |
| FileDescriptor out = new FileDescriptor(); |
| FileDescriptor err = new FileDescriptor(); |
| |
| String workingPath = (workingDirectory == null) |
| ? null |
| : workingDirectory.getPath(); |
| |
| // Ensure onExit() doesn't access the process map before we add our |
| // entry. |
| synchronized (processReferences) { |
| int pid; |
| try { |
| pid = exec(command, environment, workingPath, in, out, err, redirectErrorStream); |
| } catch (IOException e) { |
| IOException wrapper = new IOException("Error running exec()." |
| + " Command: " + Arrays.toString(command) |
| + " Working Directory: " + workingDirectory |
| + " Environment: " + Arrays.toString(environment)); |
| wrapper.initCause(e); |
| throw wrapper; |
| } |
| ProcessImpl process = new ProcessImpl(pid, in, out, err); |
| ProcessReference processReference |
| = new ProcessReference(process, referenceQueue); |
| processReferences.put(pid, processReference); |
| |
| /* |
| * This will wake up the child monitor thread in case there |
| * weren't previously any children to wait on. |
| */ |
| processReferences.notifyAll(); |
| |
| return process; |
| } |
| } |
| |
| static class ProcessImpl extends Process { |
| |
| /** Process ID. */ |
| final int id; |
| |
| final InputStream errorStream; |
| |
| /** Reads output from process. */ |
| final InputStream inputStream; |
| |
| /** Sends output to process. */ |
| final OutputStream outputStream; |
| |
| /** The process's exit value. */ |
| Integer exitValue = null; |
| final Object exitValueMutex = new Object(); |
| |
| ProcessImpl(int id, FileDescriptor in, FileDescriptor out, |
| FileDescriptor err) { |
| this.id = id; |
| |
| this.errorStream = new ProcessInputStream(err); |
| this.inputStream = new ProcessInputStream(in); |
| this.outputStream = new ProcessOutputStream(out); |
| } |
| |
| public void destroy() { |
| try { |
| kill(this.id); |
| } catch (IOException e) { |
| Logger.getLogger(Runtime.class.getName()).log(Level.FINE, |
| "Failed to destroy process " + id + ".", e); |
| } |
| } |
| |
| public int exitValue() { |
| synchronized (exitValueMutex) { |
| if (exitValue == null) { |
| throw new IllegalThreadStateException( |
| "Process has not yet terminated."); |
| } |
| |
| return exitValue; |
| } |
| } |
| |
| public InputStream getErrorStream() { |
| return this.errorStream; |
| } |
| |
| public InputStream getInputStream() { |
| return this.inputStream; |
| } |
| |
| public OutputStream getOutputStream() { |
| return this.outputStream; |
| } |
| |
| public int waitFor() throws InterruptedException { |
| synchronized (exitValueMutex) { |
| while (exitValue == null) { |
| exitValueMutex.wait(); |
| } |
| return exitValue; |
| } |
| } |
| |
| void setExitValue(int exitValue) { |
| synchronized (exitValueMutex) { |
| this.exitValue = exitValue; |
| exitValueMutex.notifyAll(); |
| } |
| } |
| |
| @Override |
| public String toString() { |
| return "Process[id=" + id + "]"; |
| } |
| } |
| |
| static class ProcessReference extends WeakReference<ProcessImpl> { |
| |
| final int processId; |
| |
| public ProcessReference(ProcessImpl referent, |
| ProcessReferenceQueue referenceQueue) { |
| super(referent, referenceQueue); |
| this.processId = referent.id; |
| } |
| } |
| |
| static class ProcessReferenceQueue extends ReferenceQueue<ProcessImpl> { |
| |
| @Override |
| public ProcessReference poll() { |
| // Why couldn't they get the generics right on ReferenceQueue? :( |
| Object reference = super.poll(); |
| return (ProcessReference) reference; |
| } |
| } |
| |
| static final ProcessManager instance = new ProcessManager(); |
| |
| /** Gets the process manager. */ |
| static ProcessManager getInstance() { |
| return instance; |
| } |
| |
| /** Automatically closes fd when collected. */ |
| private static class ProcessInputStream extends FileInputStream { |
| |
| private FileDescriptor fd; |
| |
| private ProcessInputStream(FileDescriptor fd) { |
| super(fd); |
| this.fd = fd; |
| } |
| |
| @Override |
| public void close() throws IOException { |
| try { |
| super.close(); |
| } finally { |
| synchronized (this) { |
| if (fd != null && fd.valid()) { |
| try { |
| ProcessManager.close(fd); |
| } finally { |
| fd = null; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** Automatically closes fd when collected. */ |
| private static class ProcessOutputStream extends FileOutputStream { |
| |
| private FileDescriptor fd; |
| |
| private ProcessOutputStream(FileDescriptor fd) { |
| super(fd); |
| this.fd = fd; |
| } |
| |
| @Override |
| public void close() throws IOException { |
| try { |
| super.close(); |
| } finally { |
| synchronized (this) { |
| if (fd != null && fd.valid()) { |
| try { |
| ProcessManager.close(fd); |
| } finally { |
| fd = null; |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** Closes the given file descriptor. */ |
| private static native void close(FileDescriptor fd) throws IOException; |
| } |