A complete solution for apps using root permissions
Go to file
2020-08-02 03:43:06 -07:00
busybox Update proguard rules for the service module 2020-07-28 03:44:49 -07:00
core Update CallbackList implementation 2020-08-01 05:46:22 -07:00
example Update CallbackList implementation 2020-08-01 05:46:22 -07:00
gradle/wrapper Refresh the project 2020-07-17 16:27:24 -07:00
io Update ShellInputStream to be more feature complete 2020-08-02 03:43:06 -07:00
service Properly detect APK upgrades on old platforms 2020-07-30 23:46:56 -07:00
.gitignore Initial code 2018-01-20 06:21:35 +08:00
build.gradle.kts Introduce RootService 2020-07-26 02:06:08 -07:00
CHANGELOG.md Update documentations 2020-07-24 00:08:02 -07:00
gradle.properties Update proguard rules for the service module 2020-07-28 03:44:49 -07:00
gradlew Initial code 2018-01-20 06:21:35 +08:00
gradlew.bat Initial code 2018-01-20 06:21:35 +08:00
LICENSE Create LICENSE 2018-01-25 17:53:04 +08:00
README.md Update documentations 2020-07-24 00:08:02 -07:00
settings.gradle.kts Introduce RootService 2020-07-26 02:06:08 -07:00

libsu

An Android library that provides APIs to a Unix (root) shell.

Some poorly coded applications requests a new shell (call su, or worse su -c <commands>) for every single command, which is very inefficient. This library makes sharing a single, globally shared shell session in Android applications super easy: developers won't have to bother about concurrency issues, and with a rich selection of both synchronous and asynchronous APIs, it is much easier to create a powerful root app.

Optionally, libsu comes with a full suite of I/O classes, re-creating java.io classes but enhanced with root access. Without even thinking about command-lines, you can use File, RandomAccessFile, FileInputStream, and FileOutputStream equivalents on files that are only accessible with root permissions. The I/O stream classes are carefully optimized and have very promising performance.

Also optionally, this library bundles with prebuilt BusyBox binaries. App developers can easily setup and create an internal BusyBox environment without relying on potentially flawed (or even no) external BusyBox.

One complex Android application using libsu for all root related operations is Magisk Manager.

Changelog

Javadoc

Download

android {
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}
repositories {
    maven { url 'https://jitpack.io' }
}
dependencies {
    def libsuVersion = '2.6.0'
    implementation "com.github.topjohnwu.libsu:core:${libsuVersion}"

    // Optional: For using com.topjohnwu.superuser.io classes
    implementation "com.github.topjohnwu.libsu:io:${libsuVersion}"

    // Optional: Bundle prebuilt BusyBox binaries
    // Source code of the built binaries: https://github.com/topjohnwu/ndk-busybox
    // Please note that BusyBox is GPLv2. Theoretically bundling the binary
    // without code linkage does not enforce your app to also be GPLv2, so
    // it shall be fine to use in closed source or projects using other licenses.
    implementation "com.github.topjohnwu.libsu:busybox:${libsuVersion}"
}

Quick Tutorial

Configurations

Set configurations in your MainActivity or Application class:

static {
    // Shell.Config methods shall be called before any shell is created
    // This is the why in this example we call it in a static block
    // The followings are some examples, check Javadoc for more details
    Shell.Config.setFlags(Shell.FLAG_REDIRECT_STDERR);
    Shell.Config.verboseLogging(BuildConfig.DEBUG);
    Shell.Config.setTimeout(10);
}

Shell Operations

// Run commands and get output immediately
List<String> output = Shell.su("find /dev/block -iname boot").exec().getOut();

// Aside from commands, you can also load scripts from InputStream
Shell.Result result = Shell.su(getResources().openRawResource(R.raw.script)).exec();

// You can get more stuffs from the results
int code = result.getCode();
boolean ok = result.isSuccess();
output = result.getOut();

// Run commands and output to a specific List
List<String> logs = new ArrayList<>();
Shell.su("cat /cache/magisk.log").to(logs).exec();

// Run commands in the background and don't care results
Shell.su("setenforce 0").submit();

// Run commands in the background and get results via a callback
Shell.su("sleep 5", "echo hello").submit(result -> {
    // This callback will be called on the main (UI) thread
    // after the operation is done (5 seconds after submit)
    result.getOut();  /* Should return a list with a single string "hello" */
})

// Create a reactive callback List, and update the UI on each line of output
List<String> callbackList = new CallbackList<String>() {
    @MainThread
    @Override
    public void onAddElement(String s) {
        // This callback will be called on the main (UI) thread each time
        // the list adds a new element (in this case: shell outputs a new line)
        uiUpdate(s);  /* Some method to update the UI */
    }
};
Shell.su(
    "for i in 1 2 3 4 5;do",
    "  echo $i"
    "  sleep 1"
    "done",
    "echo 'countdown done!'").to(callbackList).submit(result -> {
        // Some results cannot be acquired from callback lists
        // e.g. return codes
        uiUpdate(result.getCode());
    });

// Also get STDERR
List<String> stdout = new ArrayList<>();
List<String> stderr = new ArrayList<>();
Shell.su("echo hello", "echo hello >&2").to(stdout, stderr).exec();

I/O

Add com.github.topjohnwu.libsu:io as dependency to access the I/O wrapper classes:

// Treat files that require root access just like ordinary files
File logs = SuFile.open("/cache/magisk.log");
if (logs.exists()) {
    try (InputStream in = new SuFileInputStream(logs);
         OutputStream out = new SuFileOutputStream("/data/magisk.log.bak")) {
        // All file data can be accessed with Java Streams
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Advanced

Initialize shells with custom Shell.Initializer, similar to what .bashrc will do:

class ExampleInitializer extends Shell.Initializer {
    @Override
    public boolean onInit(Context context, Shell shell) {
        try (InputStream bashrc = context.getResources().openRawResource(R.raw.bashrc)) {
            // Load a script from raw resources
            shell.newJob()
                .add(bashrc)                            /* Load a script from resources */
                .add("export ENVIRON_VAR=SOME_VALUE")   /* Run some commands */
                .exec();
        }
        return true;
    }
}

// Register the class as an initializer
Shell.Config.setInitializers(ExampleInitializer.class);

BusyBox

The I/O classes relies on several commandline tools. Most of the tools are available in modern Android via toybox (Android 6+), however for compatibility and reliable/reproducible behavior (some applets included in toybox is not fully featured), it is a good idea to have BusyBox bundled with your app.

The BusyBox binaries are pretty large in size (1.3 - 2.1 MB for each ABI). To reduce APK size, the best option is to use either App Bundles or Split APKs. If you are not publishing to Play Store, you can also limit the supported ABIs:

android {
  defaultConfig {
    ndk {
      // Filter your supported ABIs
      abiFilters 'armeabi-v7a'
    }
  }
}

To setup BusyBox, set BusyBoxInstaller as the first shell initializer:

// Add com.github.topjohnwu.libsu:busybox as a dependency, and
// register BusyBoxInstaller as the first initializer.
Shell.Config.setInitializers(BusyBoxInstaller.class, /* other initializers */);

Example

This repo also comes with an example app (:example), check the code and play/experiment with it.