1 /*
   2  * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  20  * or visit www.oracle.com if you need additional information or have any
  21  * questions.
  22  */
  23 
  24 package jdk.test.failurehandler.action;
  25 
  26 import com.sun.tools.attach.VirtualMachine;
  27 import com.sun.tools.attach.VirtualMachineDescriptor;
  28 import jdk.test.failurehandler.value.InvalidValueException;
  29 import jdk.test.failurehandler.value.Value;
  30 import jdk.test.failurehandler.value.ValueHandler;
  31 import jdk.test.failurehandler.HtmlSection;
  32 import jdk.test.failurehandler.Stopwatch;
  33 import jdk.test.failurehandler.Utils;
  34 
  35 import java.io.BufferedReader;
  36 import java.io.CharArrayReader;
  37 import java.io.CharArrayWriter;
  38 import java.io.IOException;
  39 import java.io.InputStreamReader;
  40 import java.io.PrintWriter;
  41 import java.io.Reader;
  42 import java.io.Writer;
  43 import java.io.File;
  44 import java.nio.file.Path;
  45 import java.nio.file.Paths;
  46 import java.util.ArrayList;
  47 import java.util.Arrays;
  48 import java.util.Collections;
  49 import java.util.Date;
  50 import java.util.List;
  51 import java.util.Properties;
  52 import java.util.Timer;
  53 import java.util.TimerTask;
  54 import java.util.concurrent.TimeUnit;
  55 
  56 public class ActionHelper {
  57     private final Path workDir;
  58     @Value(name = "execSuffix")
  59     private String executableSuffix = "";
  60     private Path[] paths;
  61 
  62     private final PatternAction getChildren;
  63 
  64     public ActionHelper(Path workDir, String prefix, Properties properties,
  65                         Path... jdks) throws InvalidValueException {
  66         this.workDir = workDir.toAbsolutePath();
  67         getChildren = new PatternAction("children",
  68                 Utils.prependPrefix(prefix, "getChildren"), properties);
  69         ValueHandler.apply(this, properties, prefix);
  70         String[] pathStrings = System.getenv("PATH").split(File.pathSeparator);
  71         paths = new Path[pathStrings.length];
  72         for (int i = 0; i < paths.length; ++i) {
  73             paths[i] = Paths.get(pathStrings[i]);
  74         }
  75         addJdks(jdks);
  76     }
  77 
  78     public List<Long> getChildren(HtmlSection section, long pid) {
  79         String pidStr = "" + pid;
  80         ProcessBuilder pb = getChildren.prepareProcess(section, this, pidStr);
  81         PrintWriter log = getChildren.getSection(section).getWriter();
  82         CharArrayWriter writer = new CharArrayWriter();
  83         ExitCode code = run(log, writer, pb, getChildren.getParameters());
  84         Reader output = new CharArrayReader(writer.toCharArray());
  85 
  86         if (!ExitCode.OK.equals(code)) {
  87             log.println("WARNING: get children pids action failed");
  88             try {
  89                 Utils.copyStream(output, log);
  90             } catch (IOException e) {
  91                 e.printStackTrace(log);
  92             }
  93             return Collections.emptyList();
  94         }
  95 
  96         List<Long> result = new ArrayList<>();
  97         try {
  98             try (BufferedReader reader = new BufferedReader(output)) {
  99                 String line;
 100                 while ((line = reader.readLine()) != null) {
 101                     String value = line.trim();
 102                     if (value.isEmpty()) {
 103                         // ignore empty lines
 104                         continue;
 105                     }
 106                     try {
 107                         result.add(Long.valueOf(value));
 108                     } catch (NumberFormatException e) {
 109                         log.printf("WARNING: can't parse child pid %s : %s%n",
 110                                 line, e.getMessage());
 111                         e.printStackTrace(log);
 112                     }
 113                 }
 114             }
 115         } catch (IOException e) {
 116             e.printStackTrace(log);
 117         }
 118         return result;
 119     }
 120 
 121     public ProcessBuilder prepareProcess(PrintWriter log, String app,
 122                                          String... args) {
 123         File appBin = findApp(app);
 124         if (appBin == null) {
 125             log.printf("ERROR: can't find %s in %s.%n",
 126                     app, Arrays.toString(paths));
 127             return null;
 128         }
 129         List<String> command = new ArrayList<>(args.length + 1);
 130         command.add(appBin.toString());
 131         Collections.addAll(command, args);
 132         return new ProcessBuilder()
 133                 .command(command)
 134                 .directory(workDir.toFile());
 135     }
 136 
 137     private File findApp(String app) {
 138         String name = app + executableSuffix;
 139         for (Path pathElem : paths) {
 140             File result = pathElem.resolve(name).toFile();
 141             if (result.exists()) {
 142                 return result;
 143             }
 144         }
 145         return null;
 146     }
 147 
 148     private void addJdks(Path[] jdkPaths) {
 149         if (jdkPaths != null && jdkPaths.length != 0) {
 150             Path[] result = new Path[jdkPaths.length + paths.length];
 151             for (int i = 0; i < jdkPaths.length; ++i) {
 152                 result[i] = jdkPaths[i].resolve("bin");
 153             }
 154             System.arraycopy(paths, 0, result, jdkPaths.length, paths.length);
 155             paths = result;
 156         }
 157     }
 158 
 159     private ExitCode run(PrintWriter log, Writer out, ProcessBuilder pb,
 160                     ActionParameters params) {
 161         char[] lineChars = new char[40];
 162         Arrays.fill(lineChars, '-');
 163         String line = new String(lineChars);
 164         Stopwatch stopwatch = new Stopwatch();
 165         stopwatch.start();
 166 
 167         log.printf("%s%n[%tF %<tT] %s timeout=%s%n%1$s%n", line, new Date(), pb.command(), params.timeout);
 168 
 169         Process process;
 170         KillerTask killer;
 171 
 172         ExitCode result = ExitCode.NEVER_STARTED;
 173 
 174         try {
 175             process = pb.start();
 176             killer = new KillerTask(process);
 177             killer.schedule(params.timeout);
 178             Utils.copyStream(new InputStreamReader(process.getInputStream()),
 179                     out);
 180             try {
 181                 result = new ExitCode(process.waitFor());
 182             } catch (InterruptedException e) {
 183                 log.println("WARNING: interrupted when waiting for the tool:%n");
 184                 e.printStackTrace(log);
 185             } finally {
 186                 killer.cancel();
 187             }
 188             if (killer.hasTimedOut()) {
 189                 log.printf(
 190                         "WARNING: tool timed out: killed process after %d ms%n",
 191                         params.timeout);
 192                 result = ExitCode.TIMED_OUT;
 193             }
 194         } catch (IOException e) {
 195             log.printf("WARNING: caught IOException while running tool%n");
 196             e.printStackTrace(log);
 197             result = ExitCode.LAUNCH_ERROR;
 198         }
 199 
 200         stopwatch.stop();
 201         log.printf("%s%n[%tF %<tT] exit code: %d time: %d ms%n%1$s%n",
 202                 line, new Date(), result.value,
 203                 TimeUnit.NANOSECONDS.toMillis(stopwatch.getElapsedTimeNs()));
 204         return result;
 205     }
 206 
 207     public void runPatternAction(SimpleAction action, HtmlSection section) {
 208         if (action != null) {
 209             HtmlSection subSection = action.getSection(section);
 210             PrintWriter log = subSection.getWriter();
 211             ProcessBuilder pb = action.prepareProcess(log, this);
 212             exec(subSection, pb, action.getParameters());
 213         }
 214     }
 215 
 216     public void runPatternAction(PatternAction action, HtmlSection section,
 217                                  String value) {
 218         if (action != null) {
 219             ProcessBuilder pb = action.prepareProcess(section, this, value);
 220             HtmlSection subSection = action.getSection(section);
 221             exec(subSection, pb, action.getParameters());
 222         }
 223     }
 224 
 225     public boolean isJava(long pid, PrintWriter log) {
 226         ProcessBuilder pb = prepareProcess(log, "jps", "-q");
 227         if (pb == null) {
 228             return false;
 229         }
 230         pb.redirectErrorStream(true);
 231         boolean result = false;
 232         String pidStr = "" + pid;
 233         try {
 234             Process process = pb.start();
 235             try (BufferedReader reader = new BufferedReader(
 236                     new InputStreamReader(process.getInputStream()))) {
 237                 String line;
 238                 while ((line = reader.readLine()) != null){
 239                     if (pidStr.equals(line)) {
 240                         result = true;
 241                     }
 242                 }
 243             }
 244             process.waitFor();
 245         } catch (IOException e) {
 246             log.printf("WARNING: can't run jps : %s%n", e.getMessage());
 247             e.printStackTrace(log);
 248         } catch (InterruptedException e) {
 249             log.printf("WARNING: interrupted%n");
 250             e.printStackTrace(log);
 251         }
 252         return result;
 253     }
 254 
 255     private static class KillerTask extends TimerTask {
 256         private static final Timer WATCHDOG = new Timer("WATCHDOG", true);
 257         private final Process process;
 258         private boolean timedOut;
 259 
 260         public KillerTask(Process process) {
 261             this.process = process;
 262         }
 263 
 264         public void run() {
 265             try {
 266                 process.exitValue();
 267             } catch (IllegalThreadStateException e) {
 268                 process.destroyForcibly();
 269                 timedOut = true;
 270             }
 271         }
 272 
 273         public boolean hasTimedOut() {
 274             return timedOut;
 275         }
 276 
 277         public void schedule(long timeout) {
 278             if (timeout > 0) {
 279                 WATCHDOG.schedule(this, timeout);
 280             }
 281         }
 282     }
 283 
 284     private void exec(HtmlSection section, ProcessBuilder process,
 285                       ActionParameters params) {
 286         if (process == null) {
 287             return;
 288         }
 289         PrintWriter sectionWriter = section.getWriter();
 290         if (params.repeat > 1) {
 291             for (int i = 0, n = params.repeat; i < n; ++i) {
 292                 HtmlSection iteration = section.createChildren(
 293                         String.format("iteration_%d", i));
 294                 PrintWriter writer = iteration.getWriter();
 295                 ExitCode exitCode = run(writer, writer, process, params);
 296                 if (params.stopOnError && !ExitCode.OK.equals(exitCode)) {
 297                     sectionWriter.printf(
 298                             "ERROR: non zero exit code[%d] -- break.",
 299                             exitCode.value);
 300                     break;
 301                 }
 302                 // sleep, if this is not the last iteration
 303                 if (i < n - 1) {
 304                     try {
 305                         Thread.sleep(params.pause);
 306                     } catch (InterruptedException e) {
 307                         sectionWriter.printf(
 308                                 "WARNING: interrupted while sleeping between invocations");
 309                         e.printStackTrace(sectionWriter);
 310                     }
 311                 }
 312             }
 313         } else {
 314             run(section.getWriter(), section.getWriter(), process, params);
 315         }
 316     }
 317 
 318     /**
 319      * Special values for prepareProcess exit code.
 320      *
 321      * <p>Can we clash with normal codes?
 322      * On Solaris and Linux, only [0..255] are returned.
 323      * On Windows, prepareProcess exit codes are stored in unsigned int.
 324      * On MacOSX no limits (except it should fit C int type)
 325      * are defined in the exit() man pages.
 326      */
 327     private static class ExitCode {
 328         /** Process exits gracefully */
 329         public static final ExitCode OK = new ExitCode(0);
 330         /** Error launching prepareProcess */
 331         public static final ExitCode LAUNCH_ERROR = new ExitCode(-1);
 332         /** Application prepareProcess has been killed by watchdog due to timeout */
 333         public static final ExitCode TIMED_OUT = new ExitCode(-2);
 334         /** Application prepareProcess has never been started due to program logic */
 335         public static final ExitCode NEVER_STARTED = new ExitCode(-3);
 336 
 337         public final int value;
 338 
 339         private ExitCode(int value) {
 340             this.value = value;
 341         }
 342 
 343         @Override
 344         public boolean equals(Object o) {
 345             if (this == o) {
 346                 return true;
 347             }
 348             if (o == null || getClass() != o.getClass()) {
 349                 return false;
 350             }
 351 
 352             ExitCode exitCode = (ExitCode) o;
 353             return value == exitCode.value;
 354         }
 355 
 356         @Override
 357         public int hashCode() {
 358             return value;
 359         }
 360     }
 361 
 362 }