diff --git a/core/java/src/net/i2p/util/ShellCommand.java b/core/java/src/net/i2p/util/ShellCommand.java index a1cc2f118..70de62ea8 100644 --- a/core/java/src/net/i2p/util/ShellCommand.java +++ b/core/java/src/net/i2p/util/ShellCommand.java @@ -16,6 +16,7 @@ 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 @@ -27,55 +28,49 @@ import java.io.OutputStreamWriter; */ 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; - private boolean _commandSuccessful; - private boolean _commandCompleted; - private CommandThread _commandThread; + // 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 boolean _isTimerRunning; private OutputStream _outputStream; - private Process _process; + + /** @since 0.9.3 */ + private static class Result { + public volatile boolean commandSuccessful; + } /** * Executes a shell command in its own thread. - * Use caution when repeatedly calling execute methods with the same object - * as there are some globals here... * * @author hypercubus */ private class CommandThread extends Thread { - - private final Object caller; 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 caller, Object shellCommand, boolean consumeOutput) { - super("CommandThread"); - this.caller = caller; + CommandThread(Object shellCommand, boolean consumeOutput, Result result) { + super("ShellCommand Executor"); this.shellCommand = shellCommand; this.consumeOutput = consumeOutput; + this.result = result; } @Override public void run() { - // FIXME these will corrupt the globals if the command times out and the caller - // makes another request with the same object. - _commandSuccessful = execute(shellCommand, consumeOutput, WAIT_FOR_EXIT_STATUS); - _commandCompleted = true; - if (_isTimerRunning) { - synchronized(caller) { - caller.notifyAll(); // In case the caller is still in the wait() state. - } - } + result.commandSuccessful = execute(shellCommand, consumeOutput, WAIT_FOR_EXIT_STATUS); } } @@ -90,19 +85,16 @@ public class ShellCommand { * @author hypercubus */ private static class StreamConsumer extends Thread { - - private BufferedReader bufferedReader; - private InputStreamReader inputStreamReader; + private final BufferedReader bufferedReader; public StreamConsumer(InputStream inputStream) { - super("StreamConsumer"); - this.inputStreamReader = new InputStreamReader(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. @@ -119,27 +111,25 @@ public class ShellCommand { * Reads data from a java.io.InputStream and writes it to * STDOUT. * + * UNUSED, only for NO_CONSUME_OUTPUT + * * @author hypercubus */ private static class StreamReader extends Thread { - - private BufferedReader bufferedReader; - private InputStreamReader inputStreamReader; + private final BufferedReader bufferedReader; public StreamReader(InputStream inputStream) { - super("StreamReader"); - this.inputStreamReader = new InputStreamReader(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++) @@ -155,30 +145,26 @@ public class ShellCommand { * Reads data from STDIN and writes it to a * java.io.OutputStream. * + * UNUSED, only for NO_CONSUME_OUTPUT + * * @author hypercubus */ private static class StreamWriter extends Thread { - - private BufferedWriter bufferedWriter; - private BufferedReader in; - private OutputStreamWriter outputStreamWriter; + private final BufferedWriter bufferedWriter; public StreamWriter(OutputStream outputStream) { - super("StreamWriter"); - this.outputStreamWriter = new OutputStreamWriter(outputStream); + super("ShellCommand Writer"); + OutputStreamWriter outputStreamWriter = new OutputStreamWriter(outputStream); this.bufferedWriter = new BufferedWriter(outputStreamWriter); } @Override public void run() { - - String input; - - in = new BufferedReader(new InputStreamReader(System.in)); + BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); try { while (true) { - input = in.readLine() + "\r\n"; - bufferedWriter.write(input, 0, input.length()); + bufferedWriter.write(in.readLine()); + bufferedWriter.write("\r\n"); bufferedWriter.flush(); } } catch (IOException e) { @@ -226,11 +212,7 @@ public class ShellCommand { * else false. */ public boolean executeAndWait(String shellCommand) { - - if (execute(shellCommand, NO_CONSUME_OUTPUT, WAIT_FOR_EXIT_STATUS)) - return true; - - return false; + return execute(shellCommand, NO_CONSUME_OUTPUT, WAIT_FOR_EXIT_STATUS); } /** @@ -254,29 +236,20 @@ public class ShellCommand { * returns an exit status of 0 (indicating success), * else false. */ - public synchronized boolean executeAndWaitTimed(String shellCommand, int seconds) { - - _commandThread = new CommandThread(this, shellCommand, NO_CONSUME_OUTPUT); - _commandThread.start(); + 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) { - _isTimerRunning = true; - wait(seconds * 1000); - _isTimerRunning = false; - if (!_commandCompleted) + commandThread.join(seconds * 1000); + if (commandThread.isAlive()) return true; } - } catch (InterruptedException e) { // Wake up, time to die. } - _isTimerRunning = false; - - if (_commandSuccessful) - return true; - - return false; + return result.commandSuccessful; } /** @@ -307,11 +280,7 @@ public class ShellCommand { * else false. */ public boolean executeSilentAndWait(String shellCommand) { - - if (execute(shellCommand, CONSUME_OUTPUT, WAIT_FOR_EXIT_STATUS)) - return true; - - return false; + return execute(shellCommand, CONSUME_OUTPUT, WAIT_FOR_EXIT_STATUS); } /** @@ -332,9 +301,10 @@ public class ShellCommand { * here disables waiting. * @return true if the spawned shell process * returns an exit status of 0 (indicating success), + * OR if the time expires, * else false. */ - public synchronized boolean executeSilentAndWaitTimed(String shellCommand, int seconds) { + public boolean executeSilentAndWaitTimed(String shellCommand, int seconds) { return executeSAWT(shellCommand, seconds); } @@ -353,50 +323,65 @@ public class ShellCommand { * here disables waiting. * @return true if the spawned shell process * returns an exit status of 0 (indicating success), + * OR if the time expires, * else false. * @since 0.8.3 */ - public synchronized boolean executeSilentAndWaitTimed(String[] commandArray, int seconds) { + public boolean executeSilentAndWaitTimed(String[] commandArray, int seconds) { return executeSAWT(commandArray, seconds); } /** @since 0.8.3 */ private boolean executeSAWT(Object shellCommand, int seconds) { - _commandThread = new CommandThread(this, shellCommand, CONSUME_OUTPUT); - _commandThread.start(); - try { - - if (seconds > 0) { - _isTimerRunning = true; - wait(seconds * 1000); - _isTimerRunning = false; - if (!_commandCompleted) - return true; + 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. } - _isTimerRunning = false; - - if (_commandSuccessful) - return true; - - return false; + if (DEBUG) + System.out.println("ShellCommand returning " + result.commandSuccessful + " for \"" + name + "\" after " + (System.currentTimeMillis() - begin) + " ms"); + return result.commandSuccessful; } + /** @deprecated unused */ public InputStream getErrorStream() { return _errorStream; } + /** @deprecated unused */ public InputStream getInputStream() { return _inputStream; } + /** @deprecated unused */ 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"); @@ -410,63 +395,80 @@ public class ShellCommand { /** * @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) { - - StreamConsumer processStderrConsumer; - StreamConsumer processStdoutConsumer; - - StreamReader processStderrReader; - StreamWriter processStdinWriter; - StreamReader processStdoutReader; - + 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) - _process = Runtime.getRuntime().exec((String)shellCommand); - else if (shellCommand instanceof String[]) - _process = Runtime.getRuntime().exec((String[])shellCommand); - else + 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) { - processStderrConsumer = new StreamConsumer(_process.getErrorStream()); + Thread processStderrConsumer = new StreamConsumer(process.getErrorStream()); processStderrConsumer.start(); - processStdoutConsumer = new StreamConsumer(_process.getInputStream()); + Thread processStdoutConsumer = new StreamConsumer(process.getInputStream()); processStdoutConsumer.start(); } else { - _errorStream = _process.getErrorStream(); - _inputStream = _process.getInputStream(); - _outputStream = _process.getOutputStream(); - processStderrReader = new StreamReader(_errorStream); + // unused, consumeOutput always true + _errorStream = process.getErrorStream(); + _inputStream = process.getInputStream(); + _outputStream = process.getOutputStream(); + Thread processStderrReader = new StreamReader(_errorStream); processStderrReader.start(); - processStdinWriter = new StreamWriter(_outputStream); + Thread processStdinWriter = new StreamWriter(_outputStream); processStdinWriter.start(); - processStdoutReader = new StreamReader(_inputStream); + Thread processStdoutReader = new StreamReader(_inputStream); processStdoutReader.start(); } if (waitForExitStatus) { + if (DEBUG) + System.out.println("ShellCommand waiting for \"" + name + '\"'); try { - _process.waitFor(); + process.waitFor(); } catch (Exception e) { - + if (DEBUG) { + System.out.println("ShellCommand exception waiting for \"" + name + '\"'); + e.printStackTrace(); + } if (!consumeOutput) killStreams(); - return false; } if (!consumeOutput) killStreams(); - if (_process.exitValue() > 0) + if (DEBUG) + System.out.println("ShellCommand exit value is " + process.exitValue() + " for \"" + name + '\"'); + if (process.exitValue() > 0) return false; } } catch (Exception 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(); diff --git a/history.txt b/history.txt index 7beabaf2f..716c25697 100644 --- a/history.txt +++ b/history.txt @@ -1,5 +1,10 @@ +2012-10-10 zzz + * ShellCommand: Fix launching all browsers at startup (ticket #453) + * stats.jsp: Sort groups by translated name + 2012-10-09 zzz * Console, i2ptunnel: Warn on low ports + * EventLog: Add more events * NetDB: Increase floodfills again * RouterInfo: Exit 1 on error in main() * SSU: diff --git a/router/java/src/net/i2p/router/RouterVersion.java b/router/java/src/net/i2p/router/RouterVersion.java index 7f4edb975..31c35f8c3 100644 --- a/router/java/src/net/i2p/router/RouterVersion.java +++ b/router/java/src/net/i2p/router/RouterVersion.java @@ -18,7 +18,7 @@ public class RouterVersion { /** deprecated */ public final static String ID = "Monotone"; public final static String VERSION = CoreVersion.VERSION; - public final static long BUILD = 13; + public final static long BUILD = 14; /** for example "-test" */ public final static String EXTRA = "";