https://github.com/raynersec/libsu
Android library providing APIs to a Unix (root) shell
https://github.com/raynersec/libsu
Last synced: 10 months ago
JSON representation
Android library providing APIs to a Unix (root) shell
- Host: GitHub
- URL: https://github.com/raynersec/libsu
- Owner: RaynerSec
- License: apache-2.0
- Created: 2020-05-28T15:35:53.000Z (about 6 years ago)
- Default Branch: master
- Last Pushed: 2020-05-29T07:01:43.000Z (about 6 years ago)
- Last Synced: 2025-02-17T11:35:21.979Z (over 1 year ago)
- Language: Java
- Size: 8.84 MB
- Stars: 0
- Watchers: 2
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# libsu
[](https://jitpack.io/#topjohnwu/libsu) [](https://topjohnwu.github.io/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 `) 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 whole 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 all 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](https://github.com/topjohnwu/Magisk/tree/master/app).
## Changelog
[Link to Changelog](./CHANGELOG.md)
## Download
```java
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
def libsuVersion = '2.5.1'
implementation "com.github.topjohnwu.libsu:core:${libsuVersion}"
/* Optional: For using com.topjohnwu.superuser.io classes */
implementation "com.github.topjohnwu.libsu:io:${libsuVersion}"
/* Optional: For including prebuilt busybox binaries */
implementation "com.github.topjohnwu.libsu:busybox:${libsuVersion}"
}
```
## Quick Tutorial
### Configurations
Set configurations in your MainActivity or Application class:
```java
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
```java
// Run commands and get output immediately
List 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 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 callbackList = new CallbackList() {
@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 stuffs cannot be acquired from callback lists
* e.g. return codes */
uiUpdate(result.getCode());
});
// Also get STDERR
List stdout = new ArrayList<>();
List 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:
```java
/* 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 by Java Streams */
// For example, use a helper method to copy the logs
ShellUtils.pump(in, out);
} catch (IOException e) {
e.printStackTrace();
}
}
```
### BusyBox
The I/O classes relies on several commandline tools. *Most* of the tools are availible in modern Android via `toybox` (Android 6+), however for compatibility and reliable/reproducible behavior (some applets included in `toybox` is not fully featured), it will be a good idea to have BusyBox included to the environment:
```java
/* If you want to bundle prebuilt busybox binaries with your app,
* add com.github.topjohnwu.libsu:busybox as a dependency, and
* register BusyBoxInstaller as an initializer. */
Shell.Config.addInitializers(BusyBoxInstaller.class);
/* If your app only targets Magisk users, and you are not willing to
* increase your APK size for the busybox binaries, you can tell libsu
* to use Magisk's internal busybox */
Shell.Config.setFlags(Shell.FLAG_USE_MAGISK_BUSYBOX);
```
### Advanced
Initialize shells with custom `Shell.Initializer`, similar to what `.bashrc` will do:
```java
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.addInitializers(ExampleInitializer.class);
```
## Example
This repo also comes with an example app (`:example`), check the code and play/experiment with it.