/***************************************************************************
* Copyright (c) 2012-2013 VMware, Inc. All Rights Reserved.
* 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 com.vmware.aurora.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.util.concurrent.TimeUnit;
import org.apache.log4j.Logger;
import com.vmware.aurora.exception.CmdLineException;
/**
* A command line executor that accepts stdio input string,
* logs stdout/stderr, and generates error message from stderr.
*/
public class CommandExec extends Thread {
private static Logger logger = Logger.getLogger(CommandExec.class);
Process cmd;
BufferedReader stdout, stderr;
BufferedWriter stdin;
String input;
String errMsg;
String stdoutMsg;
String userName;
OutputHandler<?> outputHandler;
long timeout;
long timeTaken;
boolean timedOut = false;
boolean terminated = false;
public static interface OutputHandler<T> {
void processLine(String line, String userName);
T getProcessResult();
}
private CommandExec(Process cmd, String input, long timeout, String userName,
OutputHandler<?>... outputHandler) {
stdout = new BufferedReader(new InputStreamReader(cmd.getInputStream()));
stderr = new BufferedReader(new InputStreamReader(cmd.getErrorStream()));
stdin = new BufferedWriter(new OutputStreamWriter(cmd.getOutputStream()));
this.cmd = cmd;
this.userName = userName;
this.input = input;
this.timeout = TimeUnit.MILLISECONDS.toNanos(timeout);
if (outputHandler.length != 0) {
this.outputHandler = outputHandler[0];
}
}
// log available output and return the last line
private String logOutput(BufferedReader reader) {
String msg = null;
try {
while (reader.ready()) {
String line = reader.readLine();
if (line != null) {
logger.info(line);
msg = line;
if (outputHandler != null) {
outputHandler.processLine(line, userName);
}
}
}
} catch (IOException e) {
// The stream has been closed, do thing.
}
return msg;
}
/*
* This thread handles input and output of the command process.
* It also serves as a timer to kill the process.
*/
@Override
public void run() {
long startTime = System.nanoTime();
try {
// Deliver input.
if (input != null) {
stdin.write(input);
}
stdin.close();
// Wait for process to finish and get output/stderr.
while (System.nanoTime() < startTime + timeout) {
String line = logOutput(stdout);
if (line != null) {
stdoutMsg = line;
}
line = logOutput(stderr);
if (line != null) {
errMsg = line;
}
synchronized(this) {
if (terminated) {
break;
} else {
wait(1000);
}
}
}
timeTaken = System.nanoTime() - startTime;
if (timeTaken <= 0) {
timeTaken = 1;
}
// We come out of the loop because of timeout.
synchronized(this) {
if (!terminated) {
timedOut = true;
}
}
} catch (Exception e) {
logger.error(e);
}
logOutput(stdout);
// Last chance to log remaining output.
String line = logOutput(stderr);
if (line != null) {
errMsg = line;
}
// In the case of a timeout, this would stop the process and
// wake up the caller thread.
cmd.destroy();
}
/*
* Break out the running thread if the process has been terminated.
*/
public synchronized void terminate() {
terminated = true;
notifyAll();
}
public static String exec(String cmdArgs[], String input,
long timeout, OutputHandler<?>...handlers) {
return exec(cmdArgs, null, input, timeout, null, handlers);
}
public static String exec(String cmdArgs[], String input,
long timeout, String userName, OutputHandler<?>...handlers) {
return exec(cmdArgs, null, input, timeout, userName, handlers);
}
public static String exec(String cmdArgs[], File workDir, String input,
long timeout, OutputHandler<?>...handlers) {
return exec(cmdArgs, workDir, input, timeout, null, handlers);
}
public static String exec(String cmdArgs[], File workDir, String input,
long timeout, String userName, OutputHandler<?>...handlers) {
Process cmd;
CommandExec ioThread;
int retVal = 0;
InterruptedException intExc = null;
try {
cmd = Runtime.getRuntime().exec(cmdArgs, null, workDir);
} catch (IOException e) {
throw CmdLineException.EXEC_ERROR(e, cmdArgs[0]);
}
ioThread = new CommandExec(cmd, input, timeout, userName, handlers);
ioThread.start();
// Wait for the command to complete.
try {
retVal = cmd.waitFor();
} catch (InterruptedException e) {
intExc = e;
}
// Terminate the io thread if it is still in the loop.
ioThread.terminate();
try {
ioThread.join();
} catch (InterruptedException e) {
// ignore interruption
}
if (intExc != null) {
throw CmdLineException.INTERRUPTED(intExc, cmdArgs[0]);
} else if (ioThread.timedOut) {
throw CmdLineException.TIMEOUT(
TimeUnit.NANOSECONDS.toSeconds(ioThread.timeTaken), cmdArgs[0]);
} else if (retVal != 0) {
logger.info("Got " + retVal + " while executing " + cmdArgs[0]);
throw CmdLineException.COMMAND_ERROR(ioThread.errMsg);
}
return ioThread.stdoutMsg;
}
}