Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Getting output from busybox commands within Android app

For the life of me, I can't get my app to get the response from a process calling busybox from within su shell.

I've tried three different methods, as well as tried a combination of the three to get it to work, but I can never get the output from anything using busybox, only the rest of the commands.

To be more specific, I can get it to return commands like ls /data and cat suchandsuch.file, but anything that starts with "busybox" (i.e. busybox mount, busybox free) just won't show anything.

This was the method that got the closest for me, this code works with ls /data, but not "busybox free"

This one would run the command (for the most part), and return an empty string instead of loop endlessly from the inputstream.

Process p;
try {
    p = Runtime.getRuntime().exec(new String[]{"su", "-c", "/system/bin/sh"});
    DataOutputStream stdin = new DataOutputStream(p.getOutputStream());
    stdin.writeBytes("ls /data\n");
    DataInputStream stdout = new DataInputStream(p.getInputStream());
    byte[] buffer = new byte[4096];
    int read = 0;
    String out = new String();

    while(true){
        read = stdout.read(buffer);
        out += new String(buffer, 0, read);
        if(read<4096){
            break;
    }
}
    Toast.makeText(getApplicationContext(), out, Toast.LENGTH_SHORT).show();
} catch (IOException e) {
    e.printStackTrace();
}

The toast near the bottom shows everything from ls /data, but when changed to anything for busybox, its blank or null.

I've also tried both of these, but neither of them worked. (I was passing the process to them after the command was run.)

Both of these would always result in the app freezing right when you hit the button for the methods.

String termReader(Process process){
    BufferedReader reader = new BufferedReader(
        new InputStreamReader(process.getInputStream()));   
    try {
        int i;
        char[] buffer = new char[4096];
        StringBuffer output = new StringBuffer();

        while ((i = reader.read(buffer)) > 0) 
            output.append(buffer, 0, i);

        reader.close();
        return output.toString();
    } catch (IOException e) {
        e.printStackTrace();
        return e.getMessage();
    }
}


String processReader(Process process){
    InputStream stdout = process.getInputStream();
    byte[] buffer = new byte[1024];
    int read;
    String out = new String();
    while(true){
        try {
            read = stdout.read(buffer);
            out += new String(buffer, 0, read);
            if(read<1024){
            break;
        }
        } catch (IOException e) {
            e.printStackTrace();                            
        }
    }
    return out;
}

There's no stack traces to work with, so I'm starting to get a bit stumped.

Edited with the code proposed below, uhm, below :D I changed it around a small bit to make it a oneclick run thing for easier troubleshooting and testing.

This also freezes when it tries to read the inputstream, and if I call stdin.writeBytes("exit\n") before trying to read the stream it gives me the blank answer from closing the terminal, if I call it after, it loops endlessly.

void Run() {
    String command = "busybox traceroute\n";

    StringBuffer theRun = null;
    try {
        Process process = Runtime.getRuntime().exec("su");
        DataOutputStream stdin = new DataOutputStream(process.getOutputStream());
        stdin.writeBytes(command);
        BufferedReader reader = new BufferedReader(
        new InputStreamReader(process.getInputStream()));
        int read;
        char[] buffer = new char[4096];
        StringBuffer output = new StringBuffer();

        while ((read = reader.read(buffer)) > 0) {
            theRun = output.append(buffer, 0, read);
    }

    reader.close();
    process.waitFor();

    } catch (IOException e) {
        throw new RuntimeException(e);
    } catch (InterruptedException e) {
        throw new RuntimeException(e);
    }
    Toast.makeText(getApplicationContext(), theRun, Toast.LENGTH_SHORT).show();
}

It seems like its skipping the first line (the busybox info line you get every time you invoke the command) and not catching the rest of the data. Ive tried all variations I can think of to get this working right :/

If anybody's got some insight on this, I'd be greatly appreciative :)

like image 820
z3nful Avatar asked Apr 09 '12 03:04

z3nful


2 Answers

Here is a quick solution... It is a utility class I created just for this. You can use the native shell, a root shell if the device is rooted, or set a custom shell. Here you go.

https://github.com/jjNford/android-shell

like image 180
jjNford Avatar answered Oct 23 '22 02:10

jjNford


I've found a sort of a workaround for this.

First of all, running commands linked to busybox in my case would never return their output through their InputStream, no matter what method I tried (And I tried ALOT lol).

This is what I found out I could do. It's a bit tedious, and doesn't give you the full output, but if you want something to rely on whether a command fired off properly (in my case, my app just wouldn't work right if I couldn't compare how everything ran.)

You cant get the input from the process, but you CAN get the exit value if you work it right :) this works for anything that doesn't give you a complex response (like using cat on a large file)

The difference between the two is easy to find, for instance:

command = "cat /sys" // works, exits with 1

command = "cat /init.rc"  doesnt work, exits with 0

This is how I set it up to work easily. Run the commands as normal using the method provided by MasterJB:

process p;
try {
    p = Runtime.getRuntime().exec(new String[]{"su", "-c", "/system/bin/sh"});
    DataOutputStream stdin = new DataOutputStream(p.getOutputStream());
    stdin.writeBytes(command); 
    stdin.writeBytes("echo $?\n"); 

    DataInputStream stdout = new DataInputStream(p.getInputStream());
    byte[] buffer = new byte[4096];
    int read = 0;
    String out = new String();

    while(true){
        read = stdout.read(buffer);
        out += new String(buffer, 0, read);
        if(read<4096){
            break;
        }
    // here is where you catch the error value

    int len = out.length();
    char suExitValue = out.charAt(len-2);
    Toast.makeText(getApplicationContext(), String.valueOf(suExitValue), Toast.LENGTH_SHORT).show();

    return0or1(Integer.valueOf(suExitValue), command); // 0 or 1 Method
    // end catching exit value                            
}
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
}

I also found it easier to make a "0 or 1" method to return what happened. In this example it's fired as a toast. You may also want to test if the char is actually an integer, as some commands give no exit value whatsoever (weird, I know. One instance is ls /sys, this returns a blank exit value when run through a su terminal.)

String return0or1 (int returnValue, String command){
String message = command + " - Cannot get return value.";

if (returnValue == 0){
    message = command + " - successful.";
    return message;
}
if (returnValue == 1){
    message = command + " - failed.";
    return message;
}
return message;
}

With a little bit of research you can match just about any exit value with proper responses, just gotta capture them right :)

These methods return just whether the command ran (0), but if it gets a double or triple char exit code, the last digit may be 0 when it failed (i.e. when exit value is 10), so this will work in most cases, but needs to be expanded upon to catch double and triple values.

like image 24
z3nful Avatar answered Oct 23 '22 02:10

z3nful