/*
* Copyright (c) 2009, 2012, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package test.com.sun.max.vm;
import static test.com.sun.max.vm.MaxineTester.Logs.*;
import java.io.*;
import java.util.*;
import test.com.sun.max.vm.MaxineTester.Logs;
import com.sun.max.*;
import com.sun.max.io.*;
/**
* The {@code ExternalCommand} class represents an external command with input and output files.
*/
public class ExternalCommand {
public final File workingDir;
public final File stdinFile;
public final Logs logs;
public final String[] command;
public final String[] env;
public ExternalCommand(File workingDir, File stdin, Logs logs, String[] command, String[] env) {
this.stdinFile = stdin;
this.logs = logs;
this.workingDir = workingDir;
this.command = command;
this.env = env;
}
public Result exec(boolean append, int timeout) {
long start = System.currentTimeMillis();
try {
final StringBuilder sb = new StringBuilder("exec ");
for (String s : command) {
sb.append(escapeShellCharacters(s)).append(' ');
}
if (stdinFile != null) {
sb.append(" < ").append(stdinFile.getAbsolutePath());
}
if (logs.base != null) {
sb.append(append ? " >>" : " > ").append(logs.get(STDOUT).getAbsolutePath());
sb.append(append ? " 2>> " : " 2> ").append(logs.get(STDERR).getAbsolutePath());
} else {
sb.append(" > /dev/null");
sb.append(" 2>&1");
}
final String[] cmdarray = new String[] {"bash", "-c", sb.toString()};
if (logs.base != null) {
final PrintStream ps = new PrintStream(new FileOutputStream(logs.get(COMMAND)));
ps.println(Utils.toString(cmdarray, " "));
for (int i = 0; i < cmdarray.length; ++i) {
ps.println("Command array[" + i + "] = \"" + cmdarray[i] + "\"");
}
ps.println("Working directory: " + (workingDir == null ? "CWD" : workingDir.getAbsolutePath()));
ps.println("Enviroment:");
if (env != null) {
for (String def : env) {
ps.println(" " + def);
}
} else {
for (Map.Entry<String, String> entry : System.getenv().entrySet()) {
ps.println(" " + entry.getKey() + "=" + entry.getValue());
}
}
ps.close();
}
start = System.currentTimeMillis();
final Process process = Runtime.getRuntime().exec(cmdarray, env, workingDir);
final ProcessTimeoutThread processThread = new ProcessTimeoutThread(process, command[0], timeout);
final int exitValue = processThread.exitValue();
return new Result(null, exitValue, exitValue == -333, System.currentTimeMillis() - start);
} catch (Throwable t) {
return new Result(t, 0, false, System.currentTimeMillis() - start);
}
}
private static String escapeShellCharacters(String s) {
final StringBuilder sb = new StringBuilder(s.length());
for (int cursor = 0; cursor < s.length(); ++cursor) {
final char cursorChar = s.charAt(cursor);
if (cursorChar == '$') {
sb.append("\\$");
} else if (cursorChar == ' ') {
sb.append("\\ ");
} else {
sb.append(cursorChar);
}
}
return sb.toString();
}
public class Result {
public final Throwable thrown;
public final int exitValue;
public final boolean timedOut;
public final long timeMs;
Result(Throwable thrown, int exitValue, boolean timedOut, long timeMs) {
this.exitValue = exitValue;
this.timedOut = timedOut;
this.timeMs = timeMs;
this.thrown = thrown;
}
public boolean completed() {
return thrown == null && !timedOut;
}
public String checkError(Result other, OutputComparison comparison) {
if (thrown != null) {
return thrown.toString();
}
if (timedOut) {
return "timed out after " + timeMs + " ms";
}
if (exitValue != other.exitValue) {
return "exit value = " + exitValue + ", expected " + other.exitValue;
}
if (comparison.stdout && !Files.compareFiles(logs.get(STDOUT), other.command().logs.get(STDOUT), comparison.stdoutIgnore)) {
return "Standard out " + logs.get(STDOUT) + " and " + other.command().logs.get(STDOUT) + " do not match";
}
if (comparison.stderr && !Files.compareFiles(logs.get(STDERR), other.command().logs.get(STDERR), comparison.stderrIgnore)) {
return "Standard error " + logs.get(STDERR) + " and " + other.command().logs.get(STDERR) + " do not match";
}
return null;
}
ExternalCommand command() {
return ExternalCommand.this;
}
}
public static class OutputComparison {
public boolean stdout = true;
public boolean stderr = false;
public String[] stdoutIgnore;
public String[] stderrIgnore;
}
/**
* A dedicated thread to wait for the process and terminate it if it gets stuck.
*
*/
public static class ProcessTimeoutThread extends Thread {
private final Process process;
private final int timeoutMillis;
protected Integer exitValue;
private boolean timedOut;
public static final int PROCESS_TIMEOUT = -333;
public ProcessTimeoutThread(Process process, String name, int timeoutSeconds) {
super(name);
this.process = process;
this.timeoutMillis = 1000 * timeoutSeconds;
}
@Override
public void run() {
try {
// Sleep for the prescribed timeout duration
Thread.sleep(timeoutMillis);
// Not interrupted: terminate associated process
timedOut = true;
process.destroy();
} catch (InterruptedException e) {
// Process completed within timeout
}
}
public int exitValue() throws IOException {
start();
try {
exitValue = process.waitFor();
// Process exited: interrupt timeout thread so that it stops
interrupt();
} catch (InterruptedException interruptedException) {
// do nothing.
}
try {
// Wait for timeout thread to stop
join();
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
}
if (timedOut) {
exitValue = -333;
}
return exitValue;
}
}
}