Android- performing su commands programatically do

2019-03-29 08:18发布

问题:

I need my app to perform some su commands programatically (phone is rooted).

When done using adb, the commands work.

For instance: su -c "mkdir /sdcard/testdir" creates a directory called "testdir" in /sdcard.

When I call:

    p = Runtime.getRuntime().exec("su -c \"mkdir /sdcard/testdir\"");
    p.waitFor();

It just moves on and no change happens.

I tried reading the input:

DataInputStream dis = new DataInputStream(p.getInputStream());
    while((temp = dis.readLine())!=null)
        Log.d(ctx.TAG,"shell:"+temp);

But it reports nothing (loop does 0 iterations).

Has anyone ever faced this issue before? How can it be solved? Needless to day, non-su commands do work programatically with this method.

Note: I gave mkdir as an example (I know it doesn't necessarily require su). I need a lot of varied commands to be performed under su

Thank you!

EDIT: when I call su -c "id" programatically, there's output that uid=0.

回答1:

I can get stuck on a problem for days, and the moment I gather up the courage to ask about it on StackOverflow, it is solved within minutes.

The fix is:

    p=Runtime.getRuntime().exec("su");
    DataOutputStream dos = new DataOutputStream(p.getOutputStream());
    dos.writeBytes("mkdir /sdcard/testdir\n");
    dos.writeBytes("exit\n");
    dos.flush();
    dos.close();
    p.waitFor();

Don't forget \n at the end of each command you write to the DataOutputStream, as it will not work without it.



回答2:

You wrote that you "need varied commands to be performed under su". Note that the use of "Runtime.exec()" is discouraged by Chainfire, the developer of the most famous SuperSU root app.

It is tempting to use Runtime.getRuntime().exec("su -c [command]");, but you should be aware that [command] should be a single parameter, and thus may require quoting. Unfortunately both quoting the [command] parameter as well as passing the paramaters as separate variables to either Runtime.exec() or ProcessBuilder does not work consistently across all Android versions, and thus this construct should be avoided entirely. It is not impossible to do this right - but there's a high risk of problems.

See the How to SU Document. So you might want to follow his recommendation here:

3.2. Making the call

A common method to call su that avoids the known issues listed above is by creating an interactive shell and piping commands to it. This is done by calling Runtime.getRuntime().exec("su");, and retrieving input and output streams from the returned Process object. Doing this is a fairly straight-forward piece of code, but including the debug logs and checks it's a bit long to reproduce here.

The core code is located here: [libsuperuser :: Shell.java @ GitHub]. Shell.run() is a generic call to run shell code, the following more specific (static) utility functions are the ones you will probably end up using:

List<String> Shell.SH.run(String command)
List<String> Shell.SH.run(List<String> commands)
List<String> Shell.SH.run(String[] commands)

List<String> Shell.SU.run(String command)
List<String> Shell.SU.run(List<String> commands)
List<String> Shell.SU.run(String[] commands)

The SH variants are used for a non-root shell, where the SU variants are used for a root shell. These calls return a List containing the output of the shell commands. If there was no output, the list is empty, but not null. The result is only null in case an error occured - including the user not granting your app su access. These are blocking calls.

Note that in debug compiles, all shell STDIN/STDOUT/STDERR will be logged to logcat, and these calls will (intentionally) crash your app if called from the main thread. The reason for this will be discussed in section 4. When to call su.



回答3:

If you use double quotes, it will work:

su -c ""command with args""


回答4:

You might be calling Runtime.getRuntime().exec() in main thread and p.waitFor() makes your main thread wait until it executes. Try calling in another thread, like the following snippet.

new Thread(){

     @override
     public void run(){
        p = Runtime.getRuntime().exec("su -c \"mkdir /sdcard/testdir\"");
        p.waitFor();

     }.start();

}