I am using inheritIO() to redirect output from a child process in my program to the System.out and System.err, and input to System.in.
These are all redirected by System.setOut() and the like:
// Reassign System IO
System.setIn(cpanel.getConsole().getInputStream());
System.setOut(new PrintStream(cpanel.getConsole().getOutputStream()));
System.setErr(new PrintStream(cpanel.getConsole().getOutputStream()));
However when I run the process:
String[] fullargs = new String[sargs.length+4];
fullargs[0] = "java";
fullargs[1] = "-classpath"; // Runtime classpath option.
fullargs[2] = cpath; // Specify the classpath.
fullargs[3] = mname; // Specify class to run.
for(int i=0; i<sargs.length; i++)
{
fullargs[i+4] = sargs[i]; // Put together arguments.
}
ProcessBuilder proc = new ProcessBuilder()
.inheritIO()
.command(fullargs);
try
{
System.out.println("RUNNING...");
proc.start();
}
catch(IOException ioe)
{
JOptionPane.showMessageDialog(null,
"There was a system error invoking this program.",
"ERROR",
JOptionPane.ERROR_MESSAGE);
}
It redirects to what used to be System.out etc. rather than what they've been redirected to.
If I comment out the inheritIO() line, the output is lost to time and doesn't appear anywhere. With inheritIO() it goes to the standard console of the parent process rather than the redirected one. The line where I print "RUNNING" goes to the proper redirected location. In other words, inheritIO() is doing exactly what it should if I hadn't redirected the output streams of the parent process. It's going to the parent process's old console.
I have no idea why this is happening and I'm pulling my hair out here. I've seen that inheritIO() doesn't work in Windows, but this issue is the same on Mac OS and Linux. I'm using Java 7.
Please note my answer here: https://stackoverflow.com/a/32350856/5226711
Applied to your question, this means you can use an adapted verion of the StreamGobbler proposed in https://stackoverflow.com/a/14165567/5226711:
private class StreamGobbler extends Thread {
private InputStream in;
private PrintStream out;
private StreamGobbler(InputStream in, PrintStream out) {
this.in = in;
this.out = out;
}
#Override
public void run() {
try {
BufferedReader input = new BufferedReader(new InputStreamReader(in));
String line = null;
while ((line = input.readLine()) != null)
out.println(line);
} catch (IOException e) {
e.printStackTrace();
}
}
}
And use it like this:
String[] fullargs = new String[sargs.length+4];
fullargs[0] = "java";
fullargs[1] = "-classpath"; // Runtime classpath option.
fullargs[2] = cpath; // Specify the classpath.
fullargs[3] = mname; // Specify class to run.
for(int i=0; i<sargs.length; i++)
{
fullargs[i+4] = sargs[i]; // Put together arguments.
}
ProcessBuilder pb = new ProcessBuilder().command(fullargs);
try
{
System.out.println("RUNNING...");
Process p = pb.start();
StreamGobbler pOut = new StreamGobbler(p.getInputStream(), new PrintStream(cpanel.getConsole().getOutputStream()));
StreamGobbler pErr = new StreamGobbler(p.getErrorStream(), new PrintStream(cpanel.getConsole().getOutputStream()));
pOut.start();
pErr.start();
}
catch(IOException ioe)
{
JOptionPane.showMessageDialog(null,
"There was a system error invoking this program.",
"ERROR",
JOptionPane.ERROR_MESSAGE);
}
Redirecting stdin of the child is not included in my example.
Related
I've configured a PDF printer that uses Ghostscript to convert the document to a PDF, which is then processed and used by my Java desktop application. It redirects the printer data via a RedMon port. For most documents I print, it works fine and produces the PDF file as expected. However, with documents with a certain number of pages, the process simply freezes: no error is thrown, the process simply holds. It seems independent of filesize or printer properties (though the latter seems to influence the number of pages that do get printed).
After stopping the Java application, I'm left with a document with a fixed number of pages (usually 265 pages, but it also happened to end with 263 pages or 247 pages). The second to last page is incomplete (as in, partially-printed tables and texts), whereas the last page prints as an error:
ERROR: syntaxerror
OFFENDING COMMAND: --nostringval--
STACK:
/[NUMBER]
Where [NUMBER] is any given single-digit number.
Here is my Ghostscript integrator class:
public class GhostScriptIntegrator {
public static void createPDF(String[] args, String filename) {
if (args.length > 0) {
try {
Process process = Runtime.getRuntime().exec(
args[0] + " -sOutputFile=\"" + filename
+ "\" -c save pop -f -");
OutputStream os = process.getOutputStream();
BufferedReader sc = null;
try (PrintWriter writer = new PrintWriter(os)) {
sc = new BufferedReader(new InputStreamReader(System.in));
String line;
while ((line = sc.readLine()) != null) {
writer.println(line);
}
writer.flush();
} catch (Exception ex) {
Logger.getLogger(GhostScriptIntegrator.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if (sc != null) {
sc.close();
}
}
process.waitFor();
} catch (InterruptedException | IOException ex) {
Logger.getLogger(GhostScriptIntegrator.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
The args parameter is handled by my virtual printer (similarly to how it was presented in my previous post):
Full argument:
-jar "C:\Program Files (x86)\Impressora SPE\ImpressoraSPE.jar" "C:\Program Files (x86)\gs\gs9.21\bin\gswin32c -I\"C:\Program Files (x86)\gs\gs9.21\lib\" -dSAFER -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sPAPERSIZE=a4 -q -dPDFA=2 -dPDFACompatibilityPolicy=1 -dSimulateOverprint=true -dCompatibilityLevel=1.3 -dPDFSETTINGS=/screen -dEmbedAllFonts=true -dSubsetFonts=true -dAutoRotatePages=/None -dColorImageDownsampleType=/Bicubic -dColorImageResolution=150"
I have a second virtual printer that works perfectly, and there seems to be no significant difference between them: same drivers, same port arguments, same setup, very similar code. Yet, it does not freeze after a certain number of pages, and the output file is as expected.
What's causing my printer to stop responding?
It turns out there is no problem with your printer, but rather with your code. More specifically, how you [do not] handle the Runtime streams. What your process is missing is a StreamGobbler.
A StreamGobbler is an InputStream that uses an internal worker thread to constantly consume input from another InputStream. It uses a buffer to store the consumed data. The buffer size is automatically adjusted, if needed.
Your process hangs because it cannot fully read the input stream. The following articles provide a very in-depth explanation as to why it happens and how to fix it:
When Runtime.exec() won't - Part 1
When Runtime.exec() won't - Part 2
But to quote the article itself (which, in turn, quotes the JDK Javadoc):
Because some native platforms only provide limited buffer size for standard input and output streams, failure to promptly write the input stream or read the output stream of the subprocess may cause the subprocess to block, and even deadlock.
The solution is to simply exhaust each input stream from your process by implementing a StreamGobbler class:
public class GhostScriptIntegrator {
public static void createPDF(String[] args, String filename) throws FileNotFoundException {
if (args.length > 0) {
try {
Process process = Runtime.getRuntime().exec(
args[0] + " -sOutputFile=\"" + filename
+ "\" -c save pop -f -");
OutputStream os = process.getOutputStream();
BufferedReader sc = null;
InputStreamReader ir = new InputStreamReader(System.in);
try (PrintWriter writer = new PrintWriter(os)) {
StreamGobbler errorGobbler = new StreamGobbler(
process.getErrorStream(), "ERROR");
StreamGobbler outputGobbler = new StreamGobbler(
process.getInputStream(), "OUTPUT");
errorGobbler.start();
outputGobbler.start();
sc = new BufferedReader(ir);
String line;
while ((line = sc.readLine()) != null) {
writer.println(line);
writer.flush();
}
} catch (IOException ex) {
Logger.getLogger(GhostScriptIntegrator.class.getName()).log(Level.SEVERE, null, ex);
} finally {
if (sc != null) {
sc.close();
}
ir.close();
if (os != null) {
os.close();
}
}
process.waitFor();
} catch (InterruptedException | IOException ex) {
Logger.getLogger(GhostScriptIntegrator.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
}
class StreamGobbler extends Thread {
InputStream is;
String type;
StreamGobbler(InputStream is, String type) {
this.is = is;
this.type = type;
}
public void run() {
try {
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
long contador = 0;
while (br.readLine() != null) {
//Do nothing
}
} catch (IOException ex) {
Logger.getLogger(StreamGobbler.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
this is my first time posting here, so I'm not really sure what to say/ask.
Anyways, I am trying to make a simple java program that runs command prompt commands from the java program, mainly used for ping flood (ping flooding myself).
Here is my current code
public class Core extends JFrame {
JTextField ipTextField;
int packets = 0;
boolean running = false;
public Core() {
super("Fatique");
Container container = getContentPane();
JButton bAttack = new JButton("Start Attack");
JButton bStop = new JButton("Stop Attack");
JPanel jPanel = new JPanel();
container.setLayout(new FlowLayout());
ipTextField = new JTextField("IP Address", 30);
container.add(ipTextField);
bAttack.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
String input = ipTextField.getText();
String[] value = input.split(":");
int amountOfPackets = Integer.parseInt(value[1]);
exec("cmd /c" + input + " -t -n " + amountOfPackets);
running = true;
}
});
bStop.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
stop();
}
});
if(!running) {
jPanel.add(bAttack);
} else {
jPanel.add(bStop);
}
add(jPanel);
}
public void exec(String cmd) {
try {
Process p = Runtime.getRuntime().exec(cmd);
System.out.println(getOutput(p) + " - " + getPacketsSent());
} catch (IOException e) {
e.printStackTrace();
}
}
public String getOutput(Process p) {
String output = null;
try {
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = in.readLine()) != null) {
output = line;
packets++;
}
return output;
} catch (IOException e) {
System.err.println(e.getStackTrace());
}
return null;
}
public int getPacketsSent() {
return packets;
}
public void stop() {
exec("cmd /c break");
running = false;
}
public static void main(String[] args) {
Core c = new Core();
c.setSize(500, 300);
c.setVisible(true);
c.setResizable(false);
c.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
c.setLocationRelativeTo(null);
}
I'm quite new at java, so that might not do what I want it to do.
What I want it to do is I enter an ip address in the textfield, and split it with ":", and after that the amount of packets, for instance
127.0.0.1:100
Though now when I try to use that ip and packet amount, it returns "null - 0" (from exec method), and I'm not even sure if it did anything related to ping.
What I am trying to accomplish is as I already said, ping flood myself, and then output whatever I get as response, though I have no idea if this code does anything even related to that, I mostly use logic when coding java.
public String getOutput(Process p) {
String output = null;
try {
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = in.readLine()) != null) {
output = line;
packets++;
}
return output;
} catch (IOException e) {
System.err.println(e.getStackTrace());
}
return null;
}
Could someone explain me why my code code is not working how I want it to work? Please don't judge, as I already said, I'm quite new to java programming.
EDIT: Here is a quick "informative" explanation of what I am trying to accomplish.
I type in an ip address and how many packets I want to send. In this explanation, I am using localhost ip, and 5 packets.
I start the attack. At this part, I want the program to run cmd prompt command
ping 127.0.0.1 -t -n 5
127.0.0.1 being the ip that I put in the textfield in my program, and 5 is the amount of packets I put in the textfield.
I started the attack, so this is what should happen in the command prompt:
The language is Finnish, but still the same thing.
This is the basic explanation of what I am trying to accomplish, hopefully someone understood and can help/tell why my code is not working, or is working but not printing the proper lines in eclipse console.
There is a problem with your getOutput method. It looks like you intend to collect every line of output. But in fact, since you are assigning line to output, you will only return the last line before the end of stream.
To fix this, change
output = line;
to
output += line + "\n";
Or to be more correct:
output += line + LINE_SEPARATOR;
where you previously declared the latter as:
final String LINE_SEPARATOR = System.getProperty("line.separator");
That doesn't directly explain why you are getting null, but that might be because the command you are running is writing output to the 'error' stream rather than the 'output' stream.
Try something like this:
try {
Runtime rt = Runtime.getRuntime();
Process p = rt.exec("ping 192.168.16.67");
InputStream in = p.getInputStream();
OutputStream out = p.getOutputStream ();
InputStream err = p.getErrorStream();
p.destroy();
} catch(Exception exc) {}
Then, you'll have to read the out variable to parse the ping command output continuously.
bAttack.addActionListener(new ActionListener()
{
public void actionPerformed(ActionEvent e)
{
String input = ipTextField.getText();
String[] value = input.split(":");
int amountOfPackets = Integer.parseInt(value[1]);
try {
p=Runtime.getRuntime().exec("ping -n "+amountOfPackets+" "+value[0]);
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
running = true;
}
Just a small modification of your code. get output is as:
public String getOutput(Process p) {
String output = null;
try {
BufferedReader in = new BufferedReader(new InputStreamReader(p.getInputStream()));
String line = null;
while ((line = in.readLine()) != null) {
output =output+ line+"\n";
packets++;
}
return output;
} catch (IOException e) {
System.err.println(e.getStackTrace());
}
return null;
}
Here output is JTextArea I have taken to display the output of PING process. I cannot show you the output because I lack reputation.
I don't know why first line is null. Anyway, it works.
Hope this help you. Have good time coding.
Currently having troubles to redirect the output of a small windows batch console to a log file. My Java application needs to start the Runtime.exec() call without waiting it to finish and still log the output. Here is my logger class :
public class BatchThreadLogger extends Thread {
private Process process;
private String logFilePath;
private static final Logger logger = Logger.getLogger(BatchThreadLogger.class);
public BatchThreadLogger(Process process, String logFilePath) {
this.process = process;
this.logFilePath = logFilePath;
}
public void run() {
try {
// create logging file
File file = new File(logFilePath);
file.createNewFile();
// create a writer object
OutputStream os = new FileOutputStream(file);
PrintWriter pw = new PrintWriter(os);
// catch the process output in an InputStream
InputStreamReader isr = new InputStreamReader(process.getInputStream());
BufferedReader br = new BufferedReader(isr);
// wait for the process to complete
int processStatus = process.waitFor();
// redirect the output to the log file
String line = null;
while ((line = br.readLine()) != null) {
pw.println(line);
}
// add a small message with the return code to the log
pw.println("********************************************");
pw.println("********************************************");
pw.println("Batch call completed with return status " + processStatus);
pw.flush();
os.close();
}
catch (IOException e) {
logger.error("IOException raised during batch logging on file " + logFilePath, e);
}
catch (InterruptedException e) {
logger.error("InterruptedException raised during batch process execution", e);
}
}
}
My call is quite simple :
Process process = Runtime.getRuntime().exec(command);
BatchThreadLogger logger = new BatchThreadLogger(process, logFilePath);
logger.start();
My command is currently just calling my test.bat with two parameters. My test batch now just do :
echo "BATCH CALLED WITH PARAMETER %1 AND %2"
exit
My log file however does contains only :
********************************************
********************************************
Batch call completed with return status 0
I tried to place the waitFor()call before and after the code redirecting the output to the log file, without success. I always see the black screen of the command being launched, but nothing in logs...
Any help would be greatly appreciated, I'm missing something, but cannot understand what...
You're not reading from the standard error of the process you create.
I suspect that an error message is being written to the standard error, and because you're only reading from standard output, you're not picking up this error.
I would recommend replacing your use of Runtime.getRuntime().exec(...) with a ProcessBuilder, using something like the following:
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/c", "test.bat", "One", "Two");
pb.redirectErrorStream(true);
Process process = pb.start();
The line pb.redirectErrorStream(true); redirects the process' standard error into its standard output, so that you don't have to read from two streams (standard output and standard error) in two separate threads.
In other languages (like bash and Python), when we spawn a child process, this new process will inherit the stdout and stderr from the parent. This means that any output from the child process will be printed to the terminal as well as the output from the parent.
How can we achieve the same behavior in Java?
My first try was:
proc = Runtime.getRuntime().exec(cmd);
But it won't work. Based on this answer and this answer, I've replaced the code with:
ProcessBuilder pb = new ProcessBuilder(cmd);
pb.redirectOutput(System.out);
pb.redirectError(System.err);
But this doesn't even compile, as the arguments are incompatible with the expected method parameters.
You need to read the output and error streams of the process you've created and write the output to where you want it to go (in your case System.out and System.err).
Edit. The above answer is correct for java < 7. As of 7 from the javadoc it looks to be possible to instead call
processBuilder.redirectOutput(Redirect.INHERIT)
This isn't too bad. I initially said "easy", then I realized I'd had to code the InputStreamConsumer:
public static class InputStreamConsumer extends Thread {
private InputStream is;
public InputStreamConsumer(InputStream is) {
this.is = is;
}
#Override
public void run() {
try {
int value = -1;
while ((value = is.read()) != -1) {
System.out.print((char)value);
}
} catch (IOException exp) {
exp.printStackTrace();
}
}
}
private void captureOutput(Process p) {
InputStreamConsumer stdout;
InputStreamConsumer errout;
errout = new InputStreamConsumer(p.getErrorStream());
stdout = new InputStreamConsumer(p.getInputStream());
errout.start();
stdout.start();
}
....running inside of something like ...
ProcessBuilder pb = new ProcessBuilder("cmd.exe", "/D", "/C", myCommand, parm, parm...);
try {
System.out.println("Start "+myCommand);
Process myProcess = pb.start();
captureOutput(myProcess);
int returnCode = myProcess.waitFor();
System.out.println("myProcess: return code : "+returnCode);
}
catch (IOException e) {
e.printStackTrace();
}
It's certainly possible. The Ant Java task does this when fork is set true. I don't remember all the details, but on a quick look it appears that most of the magic is in PumpStreamHandler. You might try finding some inspiration from that.
I have some issues regarding ProcessBuilder.
The program is basically a simple wrapper invoking a command line script.
When running the script on its own via the terminal, the memory consumption stays below 2G.
When running the script via the java wrapper, the memory consumption explodes and even 8G is quickly filled up, resulting in out-of-memory errors.
The code to launch the process is simply:
public static int execute(String command) throws IOException
{
System.out.println("Executing: " + command);
ProcessBuilder pb = new ProcessBuilder(command.split(" +"));
Process p = pb.start();
// display any output in stderr or stdout
StreamConsumer stderr = new StreamConsumer(p.getErrorStream(), "stderr");
StreamConsumer stdout = new StreamConsumer(p.getInputStream(), "stdout");
new Thread(stderr).start();
new Thread(stdout).start();
try {
return p.waitFor();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
The StreamConsumer class is simply a class which consumes the stdout/stderr streams and display them on the console.
...the question is: why on earth does the memory consumption explode?
Regards,
Arnaud
Edit:
Whether I use ProcessBuilder or
Runtime.getRuntime.exec(...), the
result is the same.
The memory bursts tend to appear during unix 'sort' invoked by the
shell script called:
sort big-text-file > big-text-file.sorted
Edit 2 on request of Jim Garrison:
Ok, here is the StreamConsumer class which I omitted because it is rather simple:
class StreamConsumer implements Runnable
{
InputStream stream;
String descr;
StreamConsumer(InputStream stream, String descr) {
this.stream = stream;
this.descr = descr;
}
#Override
public void run()
{
String line;
BufferedReader brCleanUp =
new BufferedReader (new InputStreamReader (stream));
try {
while ((line = brCleanUp.readLine ()) != null)
System.out.println ("[" + descr + "] " + line);
brCleanUp.close();
} catch (IOException e) {
// TODO: handle exception
}
}
}
if you change your command like this :
sort -o big-text-file.sorted big-text-file
is it always the same ?
Maybe its because those StreamConsumer threads are not daemons so they don't die and get garbage collected when your processes return? You could try:
//...
final StreamConsumer stderr = new StreamConsumer(p.getErrorStream(), "stderr");
final StreamConsumer stdout = new StreamConsumer(p.getInputStream(), "stdout");
final Thread stderrThread = new Thread(stderr);
final Thread stdoutThread = new Thread(stdout);
stderrThread.setDaemon(true);
stdoutThread.setDaemon(true);
stderrThread.start();
stdoutThread.start();
//...
Is this behavior happening for single invocation or after doing this many times?