I have created a class to handle root commands in an android app, which is working just fine mostly. I create the process with Runtime.getRuntime().exec("su") and then enter commands using DataOutputStream and get the data from the commands using DataInputStream and the deprecated readLine. (I have tried using BufferedReader instead, but no difference to the issue).
My problem is that the app will hang if the command produces an error. F.eks. if I execute the command "[ -f /test ] && md5sum /test" || echo 0" I will have no problems. However, if I execute "md5sum /test" and the file does not exist, I will have to force-close the app as it will get nowhere. In this example the solution is of cause just to check for the file like in the first example, but not every situation is this simple. Issues can happen, and when they do, people should not have to force-close applications.
Is there a way to fix this issue?
Your app is likely hanging because you probably aren't processing stderr/stdout generated by the spawned process. The reason behind this is that spawned processes have very small output buffers (usually only a few kilobytes). Once those buffers are full, that process will hang until its buffers free up enough space for it to continue writing output text. I suspect you're having problems when running that second command because that second command is failing and generating lots of console output. Your child process runs out of buffer space and then tries to block until more space becomes available, which never happens.
Runtime.getRuntime().exec() returns an instance of Process. Process objects have an accessor method (I believe it's getInputStream()) that allow you to consume its stdout. You also need to do the same with getErrorStream(). Typically, I get the InputStream, then have a separate thread continually consume data from that InputStream until it's closed. You don't need to do anything with the data per se, just read it. This will tell the underlying process that it can clear its output buffers, hopefully before they ever become full (thus, causing the Process to block).
Also, I'm not 100% familiar with Android, but in plain ol' java, it's better to use ProcessBuilder to spawn child Process instances. This is because ProcessBuilder lets you combine the child's stderr AND stdout in the same stream, which lets you consume both within a single thread by just reading data from the stream returned by process.getInputStream().
Okay I have looked at ProcessBuilder (Which exists for android as well). But I still run into to a problems.
while ((line = buffer.readLine()) != null) {
data = line;
}
if (data != null && data.contains("uid=0")) {
return true;
}
This will provide the same issue as before. It get's nowhere. However, if I change the code to
while ((line = buffer.readLine()) != null) {
if (data != null && data.contains("uid=0")) {
return true;
}
}
This will work fine. The process will come to an end, and it will return true. The problem is that in order for me to use this, I would have to know exacly what to expect as the return data (Which I do in this test method). So this cannot be the solution.
Here are the entire testing method
try {
ProcessBuilder builder = new ProcessBuilder("su");
builder.redirectErrorStream(true);
Process process = builder.start();
DataOutputStream output = new DataOutputStream(process.getOutputStream());
InputStreamReader reader = new InputStreamReader(process.getInputStream());
BufferedReader buffer = new BufferedReader(reader);
output.writeBytes("id\n");
output.flush();
String line = null;
String data = null;
while ((line = buffer.readLine()) != null) {
data = line;
}
if (data != null && data.contains("uid=0")) {
return true;
}
} catch(Throwable e) {
Log.d(TAG, "Root access rejected [" + e.getClass().getName() + "] : " + e.getMessage());
}
return false;
Related
I'm trying to execute a command line to copy a folder, but nothing happens . I tried the same command from the command line and it worked fine.
code
Runtime rt = Runtime.getRuntime();
String line;
try {
Process pr = rt.exec("xcopy //E //I notts nots2");
InputStreamReader mInputStreamReader = new InputStreamReader( pr.getInputStream());
BufferedReader input = new BufferedReader( mInputStreamReader );
while ( (line = input.readLine()) != null)
System.out.println(line);
} catch (IOException e) {
ted=ted+1;
}
1) pr.getInputStream() is not enough because it will not read the error output encountered during the process execution.
You should also read the error stream : pr.getErrorStream().
2) You should specify the working directory of the process otherwise the process inherits the working directory of the current process.
For example :
Process pr = rt.exec("xcopy //E //I notts nots2", null, new File("yourWorkingDirToRunTheProcess"));
Look at the answer to this question. It explains how to consume the standard output and standard error streams of the process.
You should also consider copying files using java API methods instead of running an external process. One reason being that your command (xcopy) won't work on anything but Windows. Another reason is that running an external process is much more error prone than using standard API methods.
I am trying to execute commands on the terminal. using
ProcessBuilder builder = new ProcessBuilder("/bin/bash");
builder.redirectErrorStream(true);
Process process = builder.start();
OutputStream stdin = process.getOutputStream();
InputStream stdout = process.getInputStream();
reader = new BufferedReader(new InputStreamReader(stdout));
writer = new BufferedWriter(new OutputStreamWriter(stdin));
I am going to use this reader and and writer to continuously communicate with the process.
I'm using the following loop to read
while ((line = reader.readLine()) != null) {
System.out.println("line);
}
ISSUE: The problem here is that, when the reader starts reading from the buffer its forever in the while loop. It never exits.
I tried to put the reading in a thread
public void run() {
try {
String line;
outputText = "";
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
} catch (IOException ex) {
Logger.getLogger(ThreadReader.class.getName()).log(Level.SEVERE, null, ex);
}
}
But now i have no control on when the reader starts and finishes reading.
GOAL: I need to execute a command and read the output and then the next command.
Your Java program is communicating with an external interactive process. It has only the process's output and error streams to work with to determine how to proceed from any given point. If you want it to recognize subdivisions of the output, such as responses to individual commands, then you need to teach it what the boundaries of those subdivisions look like.
There are any number of ways you might approach the problem. For example, if the external program emits a prompt when it is ready to accept a new command, then it seems natural to watch for that prompt. Alternatively, perhaps you can tweak the input to the program so as to cause it to produce a recognizable marker at the end of each command's output.
Do also consider that this is a solved problem (many times over). The canonical utility for general-purpose scripting of interactive programs is a Tcl program called "Expect". It has inspired work-alikes in many languages, including many in Java. Google gives me three distinct examples among the first five hits, but I have no specific experience with any of them so I make no recommendation.
I've got to write down some java code that runs an external process.
This process gets an input line from stdin and gives an output line on stdout.
The code I wrote is as follows
try
{
Runtime rt = Runtime.getRuntime();
Process proc = rt.exec("./" + args[0]);
Process proc2 = rt.exec(c);
int exitVal = proc2.exitValue();
System.out.println("Process exitValue: " + exitVal);
}
catch (Throwable t){
}
Where args[0] is the external process and c is the input (String) I need to feed to the process. If it was running correctly, I would get an exitVal=0, instead I get nothing. Also, what I really want to print is the output of the external process (which would be "6" in the given example).
You must use getInputStream() and getOutputStream() on the process to communicate it, see the javadocs.
If you do not read all data from the process, it might hang and even deadlock. For a short introduction on common problems, see this blog post. At the very least, you should be reading all data from the process output.
You cannot "get nothing". Method exitValue() returns primitive type, therefore it even cannot be null. But this method can stuck however. This happens because your process is still running.
Is is possible that it is running because you did not "feed" it? In this case the only thing you have to do is to call proc2.getOutputStream() and write what you need to this stream. If you want to read the process' output use proc2.getInputStream() and read from the stream.
BTW, take a look on ProcessBuilder. It provides better, more "object oriented" API thant simple Runtime.exec().
You can feed the program input with '<' redirection:
{
Process p = Runtime.getRuntime().exec("wc < pom.xml");
p.waitFor();
System.out.println(p.exitValue());
BufferedReader bri = new BufferedReader
(new InputStreamReader(p.getInputStream()));
String line;
while ((line = bri.readLine()) != null) {
System.out.println(line);
}
bri.close();
}
Say we have this method to make an ssh to another machine. How would I get the output from that machines terminals back to the host machine
public void getSSHreply()
{
Process p;
// Set up the arguments for ProcessBuilder
String[] cmd =
{
"/usr/bin/ssh",
"someRemoteMachine", //This machine will authenticate with keys, hence no pw needed
"./myprog",
};
try
{
p = Runtime.getRuntime().exec(cmd);
//How would I redirect stdout back to host machine?
StringBuffer s = new StringBuffer();
BufferedReader input = new BufferedReader(new InputStreamReader(p.getInputStream()));
while (input.readLine() != null)
{
s.append(input.readLine() + "\n");
}
System.out.println(s.toString());
}
catch (IOException e)
{
System.err.println("Failed to read & or start ");
}
}
The Process object has methods for getting the STDOUT, STDERR, and STDIN streams. (ex. getOutputStream()).
You may want to look into commons-exec for more convenient ways to launch and manager external programs, which has tools like StreamPumper to redirect data.
Unfortunately you can't. The simplest way is probably to read p's inputStream and errorStream (normally in two separate threads).
I believe your immediate problem is because you are using a BufferedReader - so when SSH displays the "Password: " prompt (which doesn't have a terminating line feed) Bufferedreader won't be returning anything to your input.readLine() call.
The easiest thing is to read the input a single character at a time (though not the most efficient, of course).
You'll also probably want to read the stderr stream as well, which is why you might want a couple of threads.
I'm launching wkhtmltopdf from within my Java app (part of a Tomcat server, running in debug mode within Eclipse Helios on Win7 64-bit): I'd like to wait for it to complete, then Do More Stuff.
String cmd[] = {"wkhtmltopdf", htmlPathIn, pdfPathOut};
Process proc = Runtime.getRuntime().exec( cmd, null );
proc.waitFor();
But waitFor() never returns. I can still see the process in the Windows Task Manager (with the command line I passed to exec(): looks fine). AND IT WORKS. wkhtmltopdf produces the PDF I'd expect, right where I'd expect it. I can open it, rename it, whatever, even while the process is still running (before I manually terminate it).
From the command line, everything is fine:
c:\wrk>wkhtmltopdf C:\Temp\foo.html c:\wrk\foo.pdf
Loading pages (1/6)
Counting pages (2/6)
Resolving links (4/6)
Loading headers and footers (5/6)
Printing pages (6/6)
Done
The process exits just fine, and life goes on.
So what is it about runtime.exec() that's causing wkhtmltopdf to never terminate?
I could grab proc.getInputStream() and look for "Done", but that's... vile. I want something that is more general.
I've calling exec() with and without a working directory. I've tried with and without an empty "env" array. No joy.
Why is my process hanging, and what can I do to fix it?
PS: I've tried this with a couple other command line apps, and they both exhibit the same behavior.
Further exec woes.
I'm trying to read standard out & error, without success. From the command line, I know there's supposed to be something remarkably like my command line experience, but when I read the input stream returned by proc.getInputStream(), I immediately get an EOL (-1, I'm using inputStream.read()).
I checked the JavaDoc for Process, and found this
The parent process uses these streams to feed input to and get output from the subprocess. 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 [b]subprocess to block, and even deadlock[/b].
Emphasis added. So I tried that. The first 'read()' on the Standard Out inputStream blocked until I killed the process...
WITH WKHTMLTOPDF
With the generic command line ap & no params so it should "dump usage and terminate", it sucks out the appropriate std::out, then terminates.
Interesting!
JVM version issue? I'm using 1.6.0_23. The latest is... v24. I just checked the change log and don't see anything promising, but I'll try updating anyway.
Okay. Don't let the Input Streams fill or they'll block. Check. .close() can also prevent this, but isn't terribly bright.
That works in general (including the generic command line apps I've tested).
In specific however, it falls down. It appears that wkhtmltopdf is using some terminal manipulation/cursor stuff to do an ASCII-graphic progress bar. I believe this is causing the inputStream to immediately return EOF rather than giving me the correct values.
Any ideas? Hardly a deal-breaker, but it would definitely be Nice To Have.
I had the same exact issue as you and I solved it. Here are my findings:
For some reason, the output from wkhtmltopdf goes to STDERR of the process and NOT STDOUT. I have verified this by calling wkhtmltopdf from Java as well as perl
So, for example in java, you would have to do:
//ProcessBuilder is the recommended way of creating processes since Java 1.5
//Runtime.getRuntime().exec() is deprecated. Do not use.
ProcessBuilder pb = new ProcessBuilder("wkhtmltopdf.exe", htmlFilePath, pdfFilePath);
Process process = pb.start();
BufferedReader errStreamReader = new BufferedReader(new InputStreamReader(process.getErrorStream()));
//not "process.getInputStream()"
String line = errStreamReader.readLine();
while(line != null)
{
System.out.println(line); //or whatever else
line = reader.readLine();
}
On a side note, if you spawn a process from java, you MUST read from the stdout and stderr streams (even if you do nothing with it) because otherwise the stream buffer will fill and the process will hang and never return.
To futureproof your code, just in case the devs of wkhtmltopdf decide to write to stdout, you can redirect stderr of the child process to stdout and read only one stream like this:
ProcessBuilder pb = new ProcessBuilder("wkhtmltopdf.exe", htmlFilePath, pdfFilePath);
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader inStreamReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
Actually, I do this in all the cases where I have to spawn an external process from java. That way I don't have to read two streams.
You should also read the streams of the spawned process in different threads if you dont want your main thread to block, since reading from streams is blocking.
Hope this helps.
UPDATE: I raised this issue in the project page and was replied that this is by design because wkhtmltopdf supports giving the actual pdf output in STDOUT. Please see the link for more details and java code.
A process has 3 streams: input, output and error. you can read both output and error stream at the same time using separate processes. see this question and its accepted answer and also this one for example.
You should read from the streams in a different thread.
final Semaphore semaphore = new Semaphore(numOfThreads);
final String whktmlExe = tmpwhktmlExePath;
int doccount = 0;
try{
File fileObject = new File(inputDir);
for(final File f : fileObject.listFiles()) {
if(f.getAbsolutePath().endsWith(".html")) {
doccount ++;
if(doccount >500 ) {
LOG.info(" done with conversion of 1000 docs exiting ");
break;
}
System.out.println(" inside for before "+semaphore.availablePermits());
semaphore.acquire();
System.out.println(" inside for after "+semaphore.availablePermits() + " ---" +f.getName());
new java.lang.Thread() {
public void run() {
try {
String F_ = f.getName().replaceAll(".html", ".pdf") ;
ProcessBuilder pb = new ProcessBuilder(whktmlExe , f.getAbsolutePath(), outPutDir + F_ .replaceAll(" ", "_") );//"wkhtmltopdf.exe", htmlFilePath, pdfFilePath);
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader errStreamReader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line = errStreamReader.readLine();
while(line != null)
{
System.err.println(line); //or whatever else
line = errStreamReader.readLine();
}
System.out.println("after completion for ");
} catch (Exception e) {
e.printStackTrace();
}finally {
System.out.println(" in finally releasing ");
semaphore.release();
}
}
}.start();
}
}
}catch (Exception ex) {
LOG.error(" *** Error in pdf generation *** ", ex);
}
while (semaphore.availablePermits() < numOfThreads) {//till all threads finish
LOG.info( " Waiting for all threads to exit "+ semaphore.availablePermits() + " --- " +( numOfThreads - semaphore.availablePermits()));
java.lang.Thread.sleep(10000);
}