/*
* I2P - An anonymous, secure, and fully-distributed communication network.
*
* ShellCommand.java
* 2004 The I2P Project
* http://www.i2p.net
* This code is public domain.
*/
package net.i2p.util;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.util.Arrays;
/**
* Passes a command to the OS shell for execution and manages the input and
* output.
* <p>
* This class must be kept <code>gcj</code>-compatible.
*
* @author hypercubus
*/
public class ShellCommand {
private static final boolean DEBUG = false;
private static final boolean CONSUME_OUTPUT = true;
private static final boolean NO_CONSUME_OUTPUT = false;
private static final boolean WAIT_FOR_EXIT_STATUS = true;
private static final boolean NO_WAIT_FOR_EXIT_STATUS = false;
// Following are unused, only for NO_CONSUME_OUTPUT;
// need synchronization or volatile or something if we start using it.
private InputStream _errorStream;
private InputStream _inputStream;
private OutputStream _outputStream;
/** @since 0.9.3 */
private static class Result {
public volatile boolean commandSuccessful;
}
/**
* Executes a shell command in its own thread.
*
* @author hypercubus
*/
private class CommandThread extends I2PAppThread {
private final boolean consumeOutput;
private final Object shellCommand;
private final Result result;
/**
* @param shellCommand either a String or a String[] (since 0.8.3)
* @param consumeOutput always true, false is unused, possibly untested
* @param result out parameter
*/
CommandThread(Object shellCommand, boolean consumeOutput, Result result) {
super("ShellCommand Executor");
this.shellCommand = shellCommand;
this.consumeOutput = consumeOutput;
this.result = result;
}
@Override
public void run() {
result.commandSuccessful = execute(shellCommand, consumeOutput, WAIT_FOR_EXIT_STATUS);
}
}
/**
* Consumes stream data. Instances of this class, when given the
* <code>STDOUT</code> and <code>STDERR</code> input streams of a
* <code>Runtime.exec()</code> process for example, will prevent blocking
* during a <code>Process.waitFor()</code> loop and thereby allow the
* process to exit properly. This class makes no attempt to preserve the
* consumed data.
*
* @author hypercubus
*/
private static class StreamConsumer extends I2PAppThread {
private final BufferedReader bufferedReader;
public StreamConsumer(InputStream inputStream) {
super("ShellCommand Consumer");
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
this.bufferedReader = new BufferedReader(inputStreamReader);
}
@Override
public void run() {
try {
while ((bufferedReader.readLine()) != null) {
// Just like a Hoover.
}
} catch (IOException e) {
// Don't bother.
}
}
}
private final static int BUFFER_SIZE = 1024;
/**
* Reads data from a <code>java.io.InputStream</code> and writes it to
* <code>STDOUT</code>.
*
* UNUSED, only for NO_CONSUME_OUTPUT
*
* @author hypercubus
*/
private static class StreamReader extends I2PAppThread {
private final BufferedReader bufferedReader;
public StreamReader(InputStream inputStream) {
super("ShellCommand Reader");
InputStreamReader inputStreamReader = new InputStreamReader(inputStream);
this.bufferedReader = new BufferedReader(inputStreamReader);
}
@Override
public void run() {
char[] buffer = new char[BUFFER_SIZE];
int bytesRead;
try {
while (true)
while ((bytesRead = bufferedReader.read(buffer, 0, BUFFER_SIZE)) != -1)
for (int i = 0; i < bytesRead; i++)
System.out.print(buffer[i]); // TODO Pipe this to the calling thread instead of STDOUT
} catch (IOException e) {
// Don't bother.
}
}
}
/**
* Reads data from <code>STDIN</code> and writes it to a
* <code>java.io.OutputStream</code>.
*
* UNUSED, only for NO_CONSUME_OUTPUT
*
* @author hypercubus
*/
private static class StreamWriter extends I2PAppThread {
private final BufferedWriter bufferedWriter;
public StreamWriter(OutputStream outputStream) {
super("ShellCommand Writer");
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream);
this.bufferedWriter = new BufferedWriter(outputStreamWriter);
}
@Override
public void run() {
BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
try {
while (true) {
bufferedWriter.write(in.readLine());
bufferedWriter.write("\r\n");
bufferedWriter.flush();
}
} catch (IOException e) {
try {
bufferedWriter.flush();
} catch (IOException e1) {
// Eat it.
}
}
}
}
/**
* Passes a command to the shell for execution and returns immediately
* without waiting for an exit status. All output produced by the
* executed command will go to <code>STDOUT</code> and <code>STDERR</code>
* as appropriate, and can be read via {@link #getOutputStream()} and
* {@link #getErrorStream()}, respectively. Input can be passed to the
* <code>STDIN</code> of the shell process via {@link #getInputStream()}.
*
* Warning, no good way to quote or escape spaces in arguments with this method.
* @deprecated unused
*
* @param shellCommand The command for the shell to execute.
*/
@Deprecated
public void execute(String shellCommand) {
execute(shellCommand, NO_CONSUME_OUTPUT, NO_WAIT_FOR_EXIT_STATUS);
}
/**
* Passes a command to the shell for execution. This method blocks until
* all of the command's resulting shell processes have completed. All output
* produced by the executed command will go to <code>STDOUT</code> and
* <code>STDERR</code> as appropriate, and can be read via
* {@link #getOutputStream()} and {@link #getErrorStream()}, respectively.
* Input can be passed to the <code>STDIN</code> of the shell process via
* {@link #getInputStream()}.
*
* Warning, no good way to quote or escape spaces in arguments with this method.
* @deprecated unused
*
* @param shellCommand The command for the shell to execute.
* @return <code>true</code> if the spawned shell process
* returns an exit status of 0 (indicating success),
* else <code>false</code>.
*/
@Deprecated
public boolean executeAndWait(String shellCommand) {
return execute(shellCommand, NO_CONSUME_OUTPUT, WAIT_FOR_EXIT_STATUS);
}
/**
* Passes a command to the shell for execution. This method blocks until
* all of the command's resulting shell processes have completed, unless a
* specified number of seconds has elapsed first. All output produced by the
* executed command will go to <code>STDOUT</code> and <code>STDERR</code>
* as appropriate, and can be read via {@link #getOutputStream()} and
* {@link #getErrorStream()}, respectively. Input can be passed to the
* <code>STDIN</code> of the shell process via {@link #getInputStream()}.
*
* Warning, no good way to quote or escape spaces in arguments with this method.
* @deprecated unused
*
* @param shellCommand The command for the shell to execute.
* @param seconds The method will return <code>true</code> if this
* number of seconds elapses without the process
* returning an exit status. A value of <code>0</code>
* here disables waiting.
* @return <code>true</code> if the spawned shell process
* returns an exit status of 0 (indicating success),
* else <code>false</code>.
*/
@Deprecated
public boolean executeAndWaitTimed(String shellCommand, int seconds) {
Result result = new Result();
Thread commandThread = new CommandThread(shellCommand, NO_CONSUME_OUTPUT, result);
commandThread.start();
try {
if (seconds > 0) {
commandThread.join(seconds * 1000);
if (commandThread.isAlive())
return true;
}
} catch (InterruptedException e) {
// Wake up, time to die.
}
return result.commandSuccessful;
}
/**
* Passes a command to the shell for execution and returns immediately
* without waiting for an exit status. Any output produced by the executed
* command will not be displayed.
*
* Warning, no good way to quote or escape spaces in arguments with this method.
* @deprecated unused
*
* @param shellCommand The command for the shell to execute.
* @throws IOException
*/
@Deprecated
public void executeSilent(String shellCommand) throws IOException {
Runtime.getRuntime().exec(shellCommand, null);
}
/**
* Passes a command to the shell for execution. This method blocks until
* all of the command's resulting shell processes have completed. Any output
* produced by the executed command will not be displayed.
*
* Warning, no good way to quote or escape spaces in arguments with this method.
*
* @param shellCommand The command for the shell to execute.
* @return <code>true</code> if the spawned shell process
* returns an exit status of 0 (indicating success),
* else <code>false</code>.
*/
public boolean executeSilentAndWait(String shellCommand) {
return execute(shellCommand, CONSUME_OUTPUT, WAIT_FOR_EXIT_STATUS);
}
/**
* Passes a command to the shell for execution. This method blocks until
* all of the command's resulting shell processes have completed unless a
* specified number of seconds has elapsed first. Any output produced by the
* executed command will not be displayed.
*
* Warning, no good way to quote or escape spaces in arguments when shellCommand is a String.
* Use a String array for best results, especially on Windows.
*
* @param shellCommand The command for the shell to execute, as a String.
* You can't quote arguments successfully.
* See Runtime.exec(String) for more info.
* @param seconds The method will return <code>true</code> if this
* number of seconds elapses without the process
* returning an exit status. A value of <code>0</code>
* here disables waiting.
* @return <code>true</code> if the spawned shell process
* returns an exit status of 0 (indicating success),
* OR if the time expires,
* else <code>false</code>.
*/
public boolean executeSilentAndWaitTimed(String shellCommand, int seconds) {
return executeSAWT(shellCommand, seconds);
}
/**
* Passes a command to the shell for execution. This method blocks until
* all of the command's resulting shell processes have completed unless a
* specified number of seconds has elapsed first. Any output produced by the
* executed command will not be displayed.
*
* @param commandArray The command for the shell to execute,
* as a String[].
* See Runtime.exec(String[]) for more info.
* @param seconds The method will return <code>true</code> if this
* number of seconds elapses without the process
* returning an exit status. A value of <code>0</code>
* here disables waiting.
* @return <code>true</code> if the spawned shell process
* returns an exit status of 0 (indicating success),
* OR if the time expires,
* else <code>false</code>.
* @since 0.8.3
*/
public boolean executeSilentAndWaitTimed(String[] commandArray, int seconds) {
return executeSAWT(commandArray, seconds);
}
/** @since 0.8.3 */
private boolean executeSAWT(Object shellCommand, int seconds) {
String name = null;
long begin = 0;
if (DEBUG) {
if (shellCommand instanceof String) {
name = (String) shellCommand;
} else if (shellCommand instanceof String[]) {
String[] arr = (String[]) shellCommand;
name = Arrays.toString(arr);
}
begin = System.currentTimeMillis();
}
Result result = new Result();
Thread commandThread = new CommandThread(shellCommand, CONSUME_OUTPUT, result);
commandThread.start();
try {
if (seconds > 0) {
commandThread.join(seconds * 1000);
if (commandThread.isAlive()) {
if (DEBUG)
System.out.println("ShellCommand gave up waiting for \"" + name + "\" after " + seconds + " seconds");
return true;
}
}
} catch (InterruptedException e) {
// Wake up, time to die.
}
if (DEBUG)
System.out.println("ShellCommand returning " + result.commandSuccessful + " for \"" + name + "\" after " + (System.currentTimeMillis() - begin) + " ms");
return result.commandSuccessful;
}
/** @deprecated unused */
@Deprecated
public InputStream getErrorStream() {
return _errorStream;
}
/** @deprecated unused */
@Deprecated
public InputStream getInputStream() {
return _inputStream;
}
/** @deprecated unused */
@Deprecated
public OutputStream getOutputStream() {
return _outputStream;
}
/**
* Just does exec, this is NOT a test of ShellCommand.
*/
public static void main(String args[]) {
if (args.length <= 0) {
System.err.println("Usage: ShellCommand commandline");
return;
}
try {
Runtime.getRuntime().exec(args);
} catch (IOException ioe) { ioe.printStackTrace(); }
return;
}
/**
* @param shellCommand either a String or a String[] (since 0.8.3) - quick hack
* @param consumeOutput always true, false is unused, possibly untested
*/
private boolean execute(Object shellCommand, boolean consumeOutput, boolean waitForExitStatus) {
Process process;
String name = null; // for debugging only
try {
// easy way so we don't have to copy this whole method
if (shellCommand instanceof String) {
name = (String) shellCommand;
if (DEBUG)
System.out.println("ShellCommand exec \"" + name + "\" consume? " + consumeOutput + " wait? " + waitForExitStatus);
process = Runtime.getRuntime().exec(name);
} else if (shellCommand instanceof String[]) {
String[] arr = (String[]) shellCommand;
if (DEBUG) {
name = Arrays.toString(arr);
System.out.println("ShellCommand exec \"" + name + "\" consume? " + consumeOutput + " wait? " + waitForExitStatus);
}
process = Runtime.getRuntime().exec(arr);
} else {
throw new ClassCastException("shell command must be a String or a String[]");
}
if (consumeOutput) {
Thread processStderrConsumer = new StreamConsumer(process.getErrorStream());
processStderrConsumer.start();
Thread processStdoutConsumer = new StreamConsumer(process.getInputStream());
processStdoutConsumer.start();
} else {
// unused, consumeOutput always true
_errorStream = process.getErrorStream();
_inputStream = process.getInputStream();
_outputStream = process.getOutputStream();
Thread processStderrReader = new StreamReader(_errorStream);
processStderrReader.start();
Thread processStdinWriter = new StreamWriter(_outputStream);
processStdinWriter.start();
Thread processStdoutReader = new StreamReader(_inputStream);
processStdoutReader.start();
}
if (waitForExitStatus) {
if (DEBUG)
System.out.println("ShellCommand waiting for \"" + name + '\"');
try {
process.waitFor();
} catch (InterruptedException e) {
if (DEBUG) {
System.out.println("ShellCommand exception waiting for \"" + name + '\"');
e.printStackTrace();
}
if (!consumeOutput)
killStreams();
return false;
}
if (!consumeOutput)
killStreams();
if (DEBUG)
System.out.println("ShellCommand exit value is " + process.exitValue() + " for \"" + name + '\"');
if (process.exitValue() > 0)
return false;
}
} catch (IOException e) {
// probably IOException, file not found from exec()
if (DEBUG) {
System.out.println("ShellCommand execute exception for \"" + name + '\"');
e.printStackTrace();
}
return false;
}
return true;
}
/** unused, only for NO_CONSUME_OUTPUT */
private void killStreams() {
try {
_errorStream.close();
} catch (IOException e) {
// Fall through.
}
try {
_inputStream.close();
} catch (IOException e1) {
// Fall through.
}
try {
_outputStream.close();
} catch (IOException e2) {
// Fall through.
}
_errorStream = null;
_inputStream = null;
_outputStream = null;
}
}