Home | History | Annotate | Download | only in lang
      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 java.io.File;
     20 import java.io.FileDescriptor;
     21 import java.io.FileInputStream;
     22 import java.io.FileOutputStream;
     23 import java.io.IOException;
     24 import java.io.InputStream;
     25 import java.io.OutputStream;
     26 import java.lang.ref.ReferenceQueue;
     27 import java.lang.ref.WeakReference;
     28 import java.util.Arrays;
     29 import java.util.HashMap;
     30 import java.util.Map;
     31 import libcore.io.ErrnoException;
     32 import libcore.io.IoUtils;
     33 import libcore.io.Libcore;
     34 import libcore.util.MutableInt;
     35 import static libcore.io.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 
     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