I'm having a problem calling some simple command line functions with r.exec - for some reason, given a file X the command
'echo full/path/to/X' works fine (both in the display and with 'p.exitValue()==0', but 'cat full/path/to/X' does not (and has 'p.exitValue()==1') - both 'cat' and 'echo' live in /bin/ on my OSX - am I missing something? Code is below (as it happens, any suggestions to improve the code generally are welcome...)
private String takeCommand(Runtime r, String command) throws IOException {
String returnValue;
System.out.println("We are given the command" + command);
Process p = r.exec(command.split(" "));
InputStream in = p.getInputStream();
BufferedInputStream buf = new BufferedInputStream(in);
InputStreamReader inread = new InputStreamReader(buf);
BufferedReader bufferedreader = new BufferedReader(inread);
// Read the ls output
String line;
returnValue = "";
while ((line = bufferedreader.readLine()) != null) {
System.out.println(line);
returnValue = returnValue + line;
}
try {// Check for failure
if (p.waitFor() != 0) {
System.out.println("XXXXexit value = " + p.exitValue());
}
} catch (InterruptedException e) {
System.err.println(e);
} finally {
// Close the InputStream
bufferedreader.close();
inread.close();
buf.close();
in.close();
}
try {// should slow this down a little
p.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
return returnValue;
}
You should be consuming stdout and stderr asynchronously.
Otherwise it's possible for the output of the command to block the input buffers and then everything grinds to a halt (that's possibly what's happening with your cat command since it'll dump much more info than echo).
I would also not expect to have to call waitFor() twice.
Check out this SO answer for more info on output consumption, and this JavaWorld article for more Runtime.exec() pitfalls.
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);
}
}
}
To run the terminal from my intelliJ I wrote next code:
public static String runTerminalCommand (String command){
Process proc = null;
try {
proc = Runtime.getRuntime().exec(command);
} catch (IOException e) {
e.printStackTrace();
}
// Read the output
BufferedReader reader =
new BufferedReader(new InputStreamReader(proc.getInputStream()));
String line = "";
try {
while((line = reader.readLine()) != null) {
out.print(line + "\n");
return line;
}
} catch (IOException e) {
e.printStackTrace();
}
try {
proc.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
If there is a way to simplify this :)
I would first look into correctness here; that one line:
return line;
looks suspicious. Or, more precisely: are you sure that your command will always print exactly one line? Because you are returning after reading the first line; and thus omitting any other output.
Besides: you should change your code to use try-with resources instead - your return statement just leaves your readers unclosed. Probably not a problem here because all of that should go away when the process object goes away, but still: bad practice. "Cleanup things" before exiting your methods!
To answer the actual question: after looking into these conceptual things I am pointing out, there isn't much else you could do. Probably use a ProcessBuilder instead of the somehow "outdated" Runtime.exec() call.
This might be an easy one - but it's driving me nuts at this point. I'm trying to run SoX from Processing which on my mac computer is running smoothly and with no problems. I need to migrate the code to a windows 7 machine but can't get it to work for some reason. Talking to the terminal from processing works fine. I'm in the right folder (sketch data folder where SoX is also intalled) since I can run commands like "dir" etc. and get the right content printed - but as soon as I try to run sox.exe nothing happens (getting an exit value 1). Running sox.exe straight from the cmd terminal works fine. Here is a sample of what I'm trying to do:
void playBackYear (){
soxPlay = "cmd /c sox.exe year.wav -d";
println (soxPlay);
try {
File workingDir = new File(sketchPath("data"));
Process p=Runtime.getRuntime().exec(soxPlay, null, workingDir);
p.waitFor();
BufferedReader reader=new BufferedReader(
new InputStreamReader(p.getInputStream())
);
String line;
while ( (line = reader.readLine ()) != null)
{
println(line);
}
int exitVal = p.waitFor();
System.out.println("Exited with error code "+exitVal);
}
catch(IOException e1) {
System.err.println("Caught IOException: " + e1.getMessage());
System.out.println( "error 1" );
}
catch(InterruptedException e2) {
System.err.println("Caught IOException: " + e2.getMessage());
System.out.println( "error 2" );
}
}
So the questions is what am I doing wrong here?
Any help is appreciated.
I have written a small wrapper application that wraps sox binary in java. If you are interested in the full project, check it out on GitHub: sox java wrapper project
This is, how i have solved the problem:
private List<String> arguments = new ArrayList<String>();
// add sox arguments to this list above
public void execute() throws IOException {
File soxBinary = new File(soXBinaryPath);
if (!soxBinary.exists()) {
throw new FileNotFoundException("Sox binary is not available under the following path: " + soXBinaryPath);
}
arguments.add(0, soXBinaryPath);
logger.debug("Sox arguments: {}", arguments);
ProcessBuilder processBuilder = new ProcessBuilder(arguments);
processBuilder.redirectErrorStream(true);
Process process = null;
IOException errorDuringExecution = null;
try {
process = processBuilder.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null) {
logger.debug(line);
}
} catch (IOException e) {
errorDuringExecution = e;
logger.error("Error while running Sox. {}", e.getMessage());
} finally {
arguments.clear();
if (process != null) {
process.destroy();
}
if (errorDuringExecution != null) {
throw errorDuringExecution;
}
}
}
I have a program that takes in a file as an input and produces an xml file as an output. When I call this from the command line it works perfectly. I try calling it from a Java program with the following code.
try
{
Process proc = Runtime.getRuntime().exec(c);
try
{
proc.waitFor();
}
catch(InterruptedException e)
{
System.out.println("Command failed");
}
}
catch(IOException e)
{
System.out.println("Command failed");
e.printStackTrace();
}
The program seems to be running fine, as it creates an xml file; however, the xml file is empty when I open it. I'm not encountering any exceptions in my Java program, so I'm baffled as to what the problem could be. Why would the command line program work fine normally, but then when called from Java not output anything to the file it created. I was thinking maybe it was some sort of permissions thing. I tried running the program as sudo (I'm using Linux) but to no avail. This problem doesn't seem to be anything I could find an answer to online. Hopefully somebody on here might be able to tell what's going on. :)
Get the output and error streams from your process and read them to see what is happening. That should tell you what's wrong with your command.
For example:
try {
final Process proc = Runtime.getRuntime().exec("dir");
try {
proc.waitFor();
} catch (final InterruptedException e) {
e.printStackTrace();
}
final BufferedReader outputReader = new BufferedReader(new InputStreamReader(proc
.getInputStream()));
final BufferedReader errorReader = new BufferedReader(new InputStreamReader(proc
.getErrorStream()));
String line;
while ((line = outputReader.readLine()) != null) {
System.out.println(line);
}
while ((line = errorReader.readLine()) != null) {
System.err.println(line);
}
} catch (final IOException e) {
e.printStackTrace();
}
If there is no output in either stream, then I would next examine the external program and the command being sent to execute it.
Did you try launching the process from outside java?
For me, I wrote a jar file that output a file and ran that from the command line in another java program. It turns out that there was a fundamental check in my jar file that I had forgotten about on the number of characters in an input string (my bad). If the count of the characters was smaller than 8 there was no output file. If the number of characters was greater than 8, the output file came out without any trouble using the following code:
String cmdStr = "java -jar somejar.jar /home/username/outputdir 000000001";
try
{
Runtime.getRuntime().exec(cmdStr);
Runtime.getRuntime().runFinalization();
Runtime.getRuntime().freeMemory();
log.info("Done");
}
catch (IOException e)
{
log.error(System.err);
}
Not sure if I really need everything here but, hey, it works. Note: no waitFor seems to be necessary in my case.
process input (actually output of the process!) and error streams has to be handled before waiting for the process termination.
This should work better
try
{
Process proc = Runtime.getRuntime().exec("anycomand");
BufferedReader outSt = new BufferedReader(new InputStreamReader(proc.getInputStream()));
BufferedReader errSt = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
String line;
while ((line = outSt.readLine()) != null)
{
System.out.println(line);
}
while ((line = errSt.readLine()) != null)
{
System.err.println(line);
}
proc.waitFor();
}
catch (final IOException e)
{
e.printStackTrace();
}
but to understand better how Runtime exec works it is worth reading
the classic article
When Runtime.exec() won't
which provide useful sample code (better than the one above!)
NOTE: Coming back to this later as I've been unable to find a working solution. Draining the input streams manually instead of using BufferedReaders doesn't seem to help as the inputStream.read() method permanently blocks the program. I placed the gpg call in a batch file, and called the batch file from Java to only get the same result. Once gpg is called with the decrypt option, the input stream seems to become inaccessible, blocking the entire program. I'll have to come back to this when I have more time to focus on the task. In the mean time, I'll have to get decryption working by some other means (probably BouncyCastle).
The last option to probably try is to call cmd.exe, and write the command through the input stream generated by that process...
I appreciate the assistance on this issue.
I've been working on this problem for a couple days and haven't made any progress, so I thought I'd turn to the exeprtise here for some help.
I am creating a simple program that will call GnuPG via a Java runtime process. It needs to be able to encrypt and decrypt files. Encryption works, but I'm having some problems decrypting files. Whenever I try to decrypt a file, the process hangs.exitValue() always throws it's IllegalThreadStateException and the program chugs along as if it's still waiting. The code for these methods is attached below. The ultimate goal of the program is to decrypt the file, and parse it's contents in Java.
I've tried three approaches to getting the gpgDecrypt method to work. The first approach involved removing the passphrase-fd option and writing the passphrase to gpg via the gpgOutput stream in the catch block, assuming it was prompting for the passphrase like it would via the command line. This didn't work, so I put the passphrase in a file and added the -passphrase-fd option. In this case, the program repeats infinitely. If I write anything via the gpgOutput stream the program will complete. The Exit value printed will have a value of 2, and the result variable will be blank.
The third option is BouncyCastle, but I'm having problems getting it to recognize my private key (which is probably a separate post all together).
The keys I'm using to encrypt and decrypt are 4096-bit RSA keys, generated by GnuPG. In both cases using the passphrase and the passphrase file, I've tried piping the output to a file via > myFile.txt, but it doesn't seem to make any difference.
Here are the gpgEncrypt, gpgDecrypt and getStreamText methods. I posted both since the encrypt works, and I can't see any glaring differences between how I'm executing and handling the process between the encrypt and decrypt methods. getStreamText just reads the contents of the streams and returns a string.
EDIT: Quick note, Windows environment. If I copy the decrypt command output, it works via the console just fine. So I know the command is valid.
public boolean gpgEncrypt(String file, String recipient, String outputFile){
boolean success = true;
StringBuilder gpgCommand = new StringBuilder("gpg --recipient \"");
gpgCommand.append(recipient).append("\" --output \"").append(outputFile).append("\" --yes --encrypt \"");
gpgCommand.append(file).append("\"");
System.out.println("ENCRYPT COMMAND: " + gpgCommand);
try {
Process gpgProcess = Runtime.getRuntime().exec(gpgCommand.toString());
BufferedReader gpgOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
BufferedWriter gpgInput = new BufferedWriter(new OutputStreamWriter(gpgProcess.getOutputStream()));
BufferedReader gpgErrorOutput = new BufferedReader(new InputStreamReader(gpgProcess.getErrorStream()));
boolean executing = true;
while(executing){
try{
int exitValue = gpgProcess.exitValue();
if(gpgErrorOutput.ready()){
String error = getStreamText(gpgErrorOutput);
System.err.println(error);
success = false;
break;
}else if(gpgOutput.ready()){
System.out.println(getStreamText(gpgOutput));
}
executing = false;
}catch(Exception e){
//The process is not yet ready to exit. Take a break and try again.
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
System.err.println("This thread has insomnia: " + e1.getMessage());
}
}
}
} catch (IOException e) {
System.err.println("Error running GPG via runtime: " + e.getMessage());
success = false;
}
return success;
}
public String gpgDecrypt(String file, String passphraseFile){
String result = null;
StringBuilder command = new StringBuilder("gpg --passphrase-fd 0 --decrypt \"");
command.append(file).append("\" 0<\"").append(passphraseFile).append("\"");
System.out.println("DECRYPT COMMAND: " + command.toString());
try {
Process gpgProcess = Runtime.getRuntime().exec(command.toString());
BufferedReader gpgOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
BufferedReader gpgErrorOutput = new BufferedReader(new InputStreamReader(gpgProcess.getErrorStream()));
BufferedWriter gpgInput = new BufferedWriter(new OutputStreamWriter(gpgProcess.getOutputStream()));
boolean executing = true;
while(executing){
try{
if(gpgErrorOutput.ready()){
result = getStreamText(gpgErrorOutput);
System.err.println(result);
break;
}else if(gpgOutput.ready()){
result = getStreamText(gpgOutput);
}
int exitValue = gpgProcess.exitValue();
System.out.println("EXIT: " + exitValue);
executing = false;
}catch(IllegalThreadStateException e){
System.out.println("Not yet ready. Stream status: " + gpgOutput.ready() + ", error: " + gpgErrorOutput.ready());
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
System.err.println("This thread has insomnia: " + e1.getMessage());
}
}
}
} catch (IOException e) {
System.err.println("Unable to execute GPG decrypt command via command line: " + e.getMessage());
}
return result;
}
private String getStreamText(BufferedReader reader) throws IOException{
StringBuilder result = new StringBuilder();
try{
while(reader.ready()){
result.append(reader.readLine());
if(reader.ready()){
result.append("\n");
}
}
}catch(IOException ioe){
System.err.println("Error while reading the stream: " + ioe.getMessage());
throw ioe;
}
return result.toString();
}
I forget how you handle it in Java, there are 100 methods for that. But I was stuck with decrypt command itself, it was very helpful, though you didn't need all those quotes and if you wish to decrypt a large file, it goes like this:
gpg --passphrase-fd 0 --output yourfile.txt --decrypt /encryptedfile.txt.gpg/ 0</passwrdfile.txt
Have you tried to run that command from command-line, not from Java code?
There can be an issue with 'for your eyes only' option, when GnuPG will wait for console output.
This may or may not be the problem (in the decrypt function)
BufferedReader gpgOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
BufferedReader gpgErrorOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
BufferedWriter gpgInput = new BufferedWriter(new OutputStreamWriter(gpgProcess.getOutputStream()));
You are wrapping the result of getInputStream twice. Obviously gpgErrorOutput should be wrapping the error stream, not the input stream.
I stumbled upon this thread today because I was having the exact same issue as far as the program hanging. Cameron's thread from above contains the solution, which is that you have to be draining the inputStream from your process. If you don't, the stream fills up and hangs. Simply adding
String line = null;
while ( (line = gpgOutput.readLine()) != null ) {
System.out.println(line);
}
Before checking the exitValue fixed it for me.
It worked for me when i replace the decrypt command with below command
gpg --output decrypted_file --batch --passphrase "passphrase goes here" --decrypt encrypted_file
int exitValue = gpgProcess.exitValue();
// it gives process has not exited exception
The following code works with GNUPG 2.1.X
public static boolean gpgEncrypt(String file, String recipient,
String outputFile) {
boolean success = true;
StringBuilder gpgCommand = new StringBuilder("gpg --recipient \"");
gpgCommand.append(recipient).append("\" --output \"")
.append(outputFile).append("\" --yes --encrypt \"");
gpgCommand.append(file).append("\"");
System.out.println("ENCRYPT COMMAND: " + gpgCommand);
try {
Process gpgProcess = Runtime.getRuntime().exec(
gpgCommand.toString());
BufferedReader gpgOutput = new BufferedReader(
new InputStreamReader(gpgProcess.getInputStream()));
BufferedWriter gpgInput = new BufferedWriter(
new OutputStreamWriter(gpgProcess.getOutputStream()));
BufferedReader gpgErrorOutput = new BufferedReader(
new InputStreamReader(gpgProcess.getErrorStream()));
boolean executing = true;
while (executing) {
try {
int exitValue = gpgProcess.exitValue();
if (gpgErrorOutput.ready()) {
String error = getStreamText(gpgErrorOutput);
System.err.println(error);
success = false;
break;
} else if (gpgOutput.ready()) {
System.out.println(getStreamText(gpgOutput));
}
executing = false;
} catch (Exception e) {
// The process is not yet ready to exit. Take a break and
// try again.
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
System.err.println("This thread has insomnia: "
+ e1.getMessage());
}
}
}
} catch (IOException e) {
System.err.println("Error running GPG via runtime: "
+ e.getMessage());
success = false;
}
return success;
}
// gpg --pinentry-mode=loopback --passphrase "siv_test" -d -o
// "sample_enc_data_op.txt" "sample_enc_data_input.gpg"
public static String gpgDecrypt(String file, String passphrase,
String outputfile) {
String result = null;
StringBuilder command = new StringBuilder(
"gpg --pinentry-mode=loopback --passphrase \"");
command.append(passphrase).append("\" -d -o \"").append(outputfile)
.append("\" --yes \"").append(file)
.append("\"");
System.out.println("DECRYPT COMMAND: " + command.toString());
try {
Process gpgProcess = Runtime.getRuntime().exec(command.toString());
BufferedReader gpgOutput = new BufferedReader(
new InputStreamReader(gpgProcess.getInputStream()));
BufferedReader gpgErrorOutput = new BufferedReader(
new InputStreamReader(gpgProcess.getErrorStream()));
BufferedWriter gpgInput = new BufferedWriter(
new OutputStreamWriter(gpgProcess.getOutputStream()));
boolean executing = true;
while (executing) {
try {
if (gpgErrorOutput.ready()) {
result = getStreamText(gpgErrorOutput);
System.err.println(result);
break;
} else if (gpgOutput.ready()) {
result = getStreamText(gpgOutput);
}
String line = null;
while ((line = gpgOutput.readLine()) != null) {
System.out.println(line);
}
int exitValue = gpgProcess.exitValue();
System.out.println("EXIT: " + exitValue);
executing = false;
} catch (IllegalThreadStateException e) {
System.out.println("Not yet ready. Stream status: "
+ gpgOutput.ready() + ", error: "
+ gpgErrorOutput.ready());
try {
Thread.sleep(100);
} catch (InterruptedException e1) {
System.err.println("This thread has insomnia: "
+ e1.getMessage());
}
}
}
} catch (IOException e) {
System.err
.println("Unable to execute GPG decrypt command via command line: "
+ e.getMessage());
}
return result;
}
private static String getStreamText(BufferedReader reader)
throws IOException {
StringBuilder result = new StringBuilder();
try {
while (reader.ready()) {
result.append(reader.readLine());
if (reader.ready()) {
result.append("\n");
}
}
} catch (IOException ioe) {
System.err.println("Error while reading the stream: "
+ ioe.getMessage());
throw ioe;
}
return result.toString();
}