Home | History | Annotate | Download | only in share
      1 /*
      2  * Licensed to the Apache Software Foundation (ASF) under one or more
      3  * contributor license agreements.  See the NOTICE file distributed with
      4  * this work for additional information regarding copyright ownership.
      5  * The ASF licenses this file to You under the Apache License, Version 2.0
      6  * (the "License"); you may not use this file except in compliance with
      7  * the License.  You may obtain a copy of the License at
      8  *
      9  *     http://www.apache.org/licenses/LICENSE-2.0
     10  *
     11  *  Unless required by applicable law or agreed to in writing, software
     12  *  distributed under the License is distributed on an "AS IS" BASIS,
     13  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     14  *
     15  *  See the License for the specific language governing permissions and
     16  *  limitations under the License.
     17  */
     18 
     19 /**
     20  * @author Vitaly A. Provodin
     21  */
     22 
     23 package org.apache.harmony.jpda.tests.jdwp.share;
     24 
     25 import java.lang.reflect.Field;
     26 
     27 import java.io.*;
     28 import java.nio.file.*;
     29 
     30 import java.io.IOException;
     31 import java.util.Arrays;
     32 import java.util.ArrayList;
     33 import java.util.List;
     34 import java.util.Vector;
     35 
     36 import org.apache.harmony.jpda.tests.framework.LogWriter;
     37 import org.apache.harmony.jpda.tests.framework.StreamRedirector;
     38 import org.apache.harmony.jpda.tests.framework.TestErrorException;
     39 import org.apache.harmony.jpda.tests.framework.jdwp.JDWPDebuggeeWrapper;
     40 import org.apache.harmony.jpda.tests.share.JPDATestOptions;
     41 
     42 /**
     43  * This class provides basic DebuggeeWrapper implementation based on JUnit framework,
     44  * which can launch and control debuggee process.
     45  */
     46 public class JDWPUnitDebuggeeProcessWrapper extends JDWPDebuggeeWrapper {
     47 
     48     /**
     49      * Target VM debuggee process.
     50      */
     51     public Process process;
     52 
     53     protected StreamRedirector errRedir;
     54     protected StreamRedirector outRedir;
     55 
     56     /**
     57      * The expected exit code for the debuggee process.
     58      */
     59     private int expectedExitCode = 0;
     60 
     61     /**
     62      * Creates new instance with given data.
     63      *
     64      * @param settings
     65      *            test run options
     66      * @param logWriter
     67      *            where to print log messages
     68      */
     69     public JDWPUnitDebuggeeProcessWrapper(JPDATestOptions settings, LogWriter logWriter) {
     70         super(settings, logWriter);
     71     }
     72 
     73     /**
     74      * Sets the expected exit code. This is meant to be used by tests that will request target
     75      * VM termination with VirtualMachine.Exit command.
     76      */
     77     public void setExpectedExitCode(int expectedExitCode) {
     78         this.expectedExitCode = expectedExitCode;
     79     }
     80 
     81     /**
     82      * Launches process and redirects output.
     83      */
     84     public void launchProcessAndRedirectors(String cmdLine) throws IOException {
     85         logWriter.println("Launch process: " + cmdLine);
     86         process = launchProcess(cmdLine);
     87         logWriter.println("Launched process");
     88         if (process != null) {
     89             logWriter.println("Start redirectors");
     90             errRedir = new StreamRedirector(process.getErrorStream(), logWriter, "STDERR");
     91             errRedir.setDaemon(true);
     92             errRedir.start();
     93             outRedir = new StreamRedirector(process.getInputStream(), logWriter, "STDOUT");
     94             outRedir.setDaemon(true);
     95             outRedir.start();
     96             logWriter.println("Started redirectors");
     97         }
     98     }
     99 
    100     /**
    101      * Waits for process to exit and closes output redirectors
    102      */
    103     public void finishProcessAndRedirectors() {
    104         if (process != null) {
    105             try {
    106                 logWriter.println("Waiting for process exit");
    107                 WaitForProcessExit(process);
    108                 logWriter.println("Finished process");
    109             } catch (IOException e) {
    110                 throw new TestErrorException("IOException in waiting for process exit: ", e);
    111             }
    112 
    113             logWriter.println("Waiting for redirectors finish");
    114             if (outRedir != null) {
    115                 outRedir.exit();
    116                 try {
    117                     outRedir.join(settings.getTimeout());
    118                 } catch (InterruptedException e) {
    119                     logWriter.println("InterruptedException in stopping outRedirector: " + e);
    120                 }
    121                 if (outRedir.isAlive()) {
    122                     logWriter.println("WARNING: redirector not stopped: " + outRedir.getName());
    123                 }
    124             }
    125             if (errRedir != null) {
    126                 errRedir.exit();
    127                 try {
    128                     errRedir.join(settings.getTimeout());
    129                 } catch (InterruptedException e) {
    130                     logWriter.println("InterruptedException in stopping errRedirector: " + e);
    131                 }
    132                 if (errRedir.isAlive()) {
    133                     logWriter.println("WARNING: redirector not stopped: " + errRedir.getName());
    134                 }
    135             }
    136             logWriter.println("Finished redirectors");
    137         }
    138     }
    139 
    140     /**
    141      * Launches process with given command line.
    142      *
    143      * @param cmdLine
    144      *            command line
    145      * @return associated Process object or null if not available
    146      * @throws IOException
    147      *             if error occurred in launching process
    148      */
    149     protected Process launchProcess(String cmdLine) throws IOException {
    150 
    151     	// Runtime.exec(String) does not preserve quoted arguments
    152     	// process = Runtime.getRuntime().exec(cmdLine);
    153 
    154     	String args[] = splitCommandLine(cmdLine);
    155     	process = Runtime.getRuntime().exec(args);
    156         return process;
    157     }
    158 
    159     /**
    160      * Splits command line into arguments preserving spaces in quoted arguments
    161      * either with single and double quotes (not prefixed by '\').
    162      *
    163      * @param cmd
    164      *            command line
    165      * @return associated Process object or null if not available
    166      */
    167 /*
    168     public String[] splitCommandLine(String cmd) {
    169 
    170         // allocate array for parsed arguments
    171         int max_argc = 250;
    172         Vector argv = new Vector();
    173 
    174         // parse command line
    175         int len = cmd.length();
    176         if (len > 0) {
    177             for (int arg = 0; arg < len;) {
    178                 // skip initial spaces
    179                 while (Character.isWhitespace(cmd.charAt(arg))) arg++;
    180                 // parse non-spaced or quoted argument
    181                 for (int p = arg; ; p++) {
    182                     // check for ending separator
    183                     if (p >= len || Character.isWhitespace(cmd.charAt(p))) {
    184                     	if (p > len) p = len;
    185                     	String val = cmd.substring(arg, p);
    186                         argv.add(val);
    187                         arg = p + 1;
    188                         break;
    189                     }
    190 
    191                     // check for starting quote
    192                     if (cmd.charAt(p) == '\"') {
    193                          char quote = cmd.charAt(p++);
    194                          // skip all chars until terminating quote or end of line
    195                          for (; p < len; p++) {
    196                              // check for terminating quote
    197                              if (cmd.charAt(p) == quote)
    198                             	 break;
    199                              // skip escaped quote
    200                              if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == quote)
    201                             	 p++;
    202                          }
    203                      }
    204 
    205                     // skip escaped quote
    206                     if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == '\"') {
    207                     	p++;
    208                     }
    209                 }
    210             }
    211         }
    212 
    213     	logWriter.println("Splitted command line: " + argv);
    214         int size = argv.size();
    215         String args[] = new String[size];
    216         return (String[])argv.toArray(args);
    217 	}
    218 */
    219     public String[] splitCommandLine(String cmd) {
    220 
    221         int len = cmd.length();
    222         char chars[] = new char[len];
    223         Vector<String> argv = new Vector<String>();
    224 
    225         if (len > 0) {
    226             for (int arg = 0; arg < len;) {
    227                 // skip initial spaces
    228                 while (Character.isWhitespace(cmd.charAt(arg))) arg++;
    229                 // parse non-spaced or quoted argument
    230                 for (int p = arg, i = 0; ; p++) {
    231                     // check for starting quote
    232                     if (p < len && (cmd.charAt(p) == '\"' || cmd.charAt(p) == '\'')) {
    233                          char quote = cmd.charAt(p++);
    234                          // copy all chars until terminating quote or end of line
    235                          for (; p < len; p++) {
    236                              // check for terminating quote
    237                              if (cmd.charAt(p) == quote) {
    238                             	 p++;
    239                             	 break;
    240                              }
    241                              // preserve escaped quote
    242                              if (cmd.charAt(p) == '\\' && (p+1) < len && cmd.charAt(p+1) == quote)
    243                             	 p++;
    244                              chars[i++] = cmd.charAt(p);
    245                          }
    246                      }
    247 
    248                     // check for ending separator
    249                     if (p >= len || Character.isWhitespace(cmd.charAt(p))) {
    250                     	String val = new String(chars, 0, i);
    251                         argv.add(val);
    252                         arg = p + 1;
    253                         break;
    254                     }
    255 
    256                     // preserve escaped quote
    257                     if (cmd.charAt(p) == '\\' && (p+1) < len
    258                     		&& (cmd.charAt(p+1) == '\"' || cmd.charAt(p+1) == '\'')) {
    259                     	p++;
    260                     }
    261 
    262                     // copy current char
    263                     chars[i++] = cmd.charAt(p);
    264                 }
    265             }
    266         }
    267 
    268     	logWriter.println("Splitted command line: " + argv);
    269         int size = argv.size();
    270         String args[] = new String[size];
    271         return argv.toArray(args);
    272 	}
    273 
    274     /**
    275      * Waits for launched process to exit.
    276      *
    277      * @param process
    278      *            associated Process object or null if not available
    279      * @throws IOException
    280      *             if any exception occurs in waiting
    281      */
    282     protected void WaitForProcessExit(Process process) throws IOException {
    283         ProcessWaiter thrd = new ProcessWaiter();
    284         thrd.setDaemon(true);
    285         thrd.start();
    286         try {
    287             thrd.join(settings.getTimeout());
    288         } catch (InterruptedException e) {
    289             throw new TestErrorException(e);
    290         }
    291 
    292         if (thrd.isAlive()) {
    293             // ProcessWaiter thread is still running (after we wait until a timeout) but is
    294             // waiting for the debuggee process to exit. We send an interrupt request to
    295             // that thread so it receives an InterrupedException and terminates.
    296             thrd.interrupt();
    297         }
    298 
    299         try {
    300             int exitCode = process.exitValue();
    301             logWriter.println("Finished debuggee with exit code: " + exitCode);
    302             if (exitCode != expectedExitCode) {
    303                 throw new TestErrorException("Debuggee exited with code " + exitCode +
    304                         " but we expected code " + expectedExitCode);
    305             }
    306         } catch (IllegalThreadStateException e) {
    307             logWriter.println("Terminate debuggee process with " + e);
    308             GetRemoteStackTrace(process);
    309             throw new TestErrorException("Debuggee process did not finish during timeout", e);
    310         } finally {
    311             // dispose any resources of the process
    312             process.destroy();
    313         }
    314     }
    315 
    316     private int GetRealPid(Process process) {
    317       int initial_pid = GetInitialPid(process);
    318       logWriter.println("initial pid: " + initial_pid);
    319       String[] names = new String[] { "java", "dalvikvm", "dalvikvm32", "dalvikvm64" };
    320       return FindPidFor(Paths.get("/proc").resolve(Integer.toString(initial_pid)), names);
    321     }
    322 
    323     private int getPid(Path proc) throws IOException {
    324       try {
    325         // See man 5 proc for information on stat file. All we really care about is that it is a
    326         // list of various pieces of information about the process separated by ' '. The first of
    327         // these is the PID.
    328         return Integer.valueOf(new String(Files.readAllBytes(proc.resolve("stat"))).split(" ")[0]);
    329       } catch (IOException e) {
    330         return -1;
    331       }
    332     }
    333 
    334     private int FindPidFor(Path cur, String[] names) {
    335       try {
    336         if (!cur.toFile().exists()) {
    337           return -1;
    338         }
    339         int pid = getPid(cur);
    340         if (Arrays.stream(names).anyMatch(
    341             (x) -> {
    342               try {
    343                 return cur.resolve("exe").toRealPath().endsWith(x);
    344               } catch (Exception e) {
    345                 return false;
    346               }
    347             })) {
    348           return pid;
    349         } else {
    350           Path children = cur.resolve("task").resolve(Integer.toString(pid)).resolve("children");
    351           if (!children.toFile().exists()) {
    352             return -1;
    353           } else {
    354             for (String child_pid : ReadChildPids(children.toFile())) {
    355               logWriter.println("Examining pid: " + child_pid);
    356               int res = FindPidFor(Paths.get("/proc").resolve(child_pid), names);
    357               if (res != -1) {
    358                 return res;
    359               }
    360             }
    361             return -1;
    362           }
    363         }
    364       } catch (Exception e) {
    365         return -1;
    366       }
    367     }
    368 
    369     private String[] ReadChildPids(File children) throws Exception {
    370       if (!children.exists()) {
    371         return new String[0];
    372       } else {
    373         return new String(Files.readAllBytes(children.toPath())).split(" ");
    374       }
    375     }
    376 
    377     private int GetInitialPid(Process process) {
    378       try {
    379         Class<?> unix_process_class = Class.forName("java.lang.UNIXProcess");
    380         if (!process.getClass().equals(unix_process_class)) {
    381           throw new TestErrorException("Process is of unexpected type. Timeout happened.");
    382         }
    383         Field pid = unix_process_class.getDeclaredField("pid");
    384         pid.setAccessible(true);
    385         return (int)pid.get(process);
    386       } catch (ReflectiveOperationException e) {
    387         throw new TestErrorException("Unable to get pid from process to debug timeout!", e);
    388       }
    389     }
    390 
    391     private void GetRemoteStackTrace(Process process) throws IOException {
    392       String pid = Integer.toString(GetRealPid(process));
    393       logWriter.println("trying to dump " + pid);
    394       List<String> cmd = new ArrayList<>(Arrays.asList(splitCommandLine(settings.getDumpProcessCommand())));
    395       cmd.add(pid);
    396       logWriter.println("command: " + cmd);
    397 
    398       ProcessBuilder b = new ProcessBuilder(cmd).redirectErrorStream(true);
    399       Process p = null;
    400       StreamRedirector out = null;
    401       try {
    402         p = b.start();
    403         out = new StreamRedirector(p.getInputStream(), logWriter, "dump-" + pid);
    404         out.setDaemon(true);
    405         out.start();
    406         p.waitFor();
    407         // Wait 5 seconds for the streams to catch up.
    408         Thread.sleep(5000);
    409       } catch (Exception e) {
    410         throw new TestErrorException("Unable to get process dump!", e);
    411       } finally {
    412         if (p != null) {
    413           p.destroy();
    414         }
    415         if (out != null) {
    416           try {
    417             out.exit();
    418             out.join();
    419           } catch (Exception e) {
    420             logWriter.println("Suppressing error: " + e);
    421           }
    422         }
    423       }
    424     }
    425 
    426     /**
    427      * Separate thread for waiting for process exit for specified timeout.
    428      */
    429     class ProcessWaiter extends Thread {
    430         @Override
    431         public void run() {
    432             try {
    433                 process.waitFor();
    434             } catch (InterruptedException e) {
    435                 logWriter.println("Ignoring exception in ProcessWaiter thread interrupted: " + e);
    436             }
    437         }
    438     }
    439 
    440     @Override
    441     public void start() {
    442     }
    443 
    444     @Override
    445     public void stop() {
    446     }
    447 }
    448