topjohnwu / libsu Goto Github PK
View Code? Open in Web Editor NEWA complete solution for apps using root permissions
License: Apache License 2.0
A complete solution for apps using root permissions
License: Apache License 2.0
As the title says...
You've documented the code, but the method which compiles and publishes it to Jitpack removes the documentation, and when Gradle downloads the dependency the Javadoc is gone.
It's possible to locally attach the HTML Javadoc to the library, but if I do that it still loads slowly every time I try to open a method's doc
It seems Jake Wharton's Timber for example serves the Javadoc as it should, but I also notice that if I open Timber class the IDE does not show it's decompiled. Maybe the key is to serve the dependency as a source jar, and not as a compiled code jar. As far as I know closed source projects use the latter, but since libsu is open source you could serve the source jar.
Could you take a look at this, please?
This could be helpful to prevent OOMs in situations like copying a large file or reading logcat.
EDIT: By the way, my Crashlytics indeed caught OOMs when my code was attempting to save logcat to a file.
On this line:
val gotRoot = Shell.getCachedShell()?.isRoot == true || Shell.newInstance().isRoot
I got this error:
Caused by b.c.a.a: Unable to create a shell!
at com.topjohnwu.superuser.Shell.newInstance(Shell.java:278)
at com.topjohnwu.superuser.Shell.newInstance(Shell.java:239)
Caused by java.io.IOException: Shell timeout
at com.topjohnwu.superuser.internal.ShellImpl.<init>(ShellImpl.java:144)
at com.topjohnwu.superuser.internal.Factory.createShell(Factory.java:27)
at com.topjohnwu.superuser.Shell.newInstance(Shell.java:255)
at com.topjohnwu.superuser.Shell.newInstance(Shell.java:239)
This was on a bit older version of the library (2.5.0) but I don't see it being mentioned in the changelog.
Seems it occurs on Android 4.3, on RIM devices.
Hi. i test:
Shell.su("magiskhide status").to(out).exec();
out is empty. is there something wrong ?
i use latest ver 3.0.2
Hello,
I'm trying to use this library to install split-APK files, as this one does:
More specifically, I'm trying to convert this code of the above, which pushes the file as installation source to the "pm install-write" command :
while (apkSource.nextApk())
ensureCommandSucceeded(Root.exec(String.format("pm install-write -S %d %d \"%s\"", apkSource.getApkLength(), sessionId, apkSource.getApkName()), apkSource.openApkInputStream()));
And the code for its root class is probably as such:
private static Result execInternal(String command, @Nullable InputStream inputPipe) {
try {
Process process = Runtime.getRuntime().exec(String.format("su -c %s", command));
StringBuilder stdOutSb = new StringBuilder();
StringBuilder stdErrSb = new StringBuilder();
Thread stdOutD = writeStreamToStringBuilder(stdOutSb, process.getInputStream());
Thread stdErrD = writeStreamToStringBuilder(stdErrSb, process.getErrorStream());
if (inputPipe != null) {
IOUtils.copyStream(inputPipe, process.getOutputStream());
inputPipe.close();
process.getOutputStream().close();
}
process.waitFor();
stdOutD.join();
stdErrD.join();
return new Result(command, process.exitValue(), stdOutSb.toString().trim(), stdErrSb.toString().trim());
} catch (Exception e) {
Log.w(TAG, "Unable execute command: ");
Log.w(TAG, e);
return new Result(command, -1, "", "Java exception: " + Utils.throwableToString(e));
}
}
So, what I tried using this library, is this :
for (apkSource in fileInfoList) {
// pm install-write -S %d %d \"%s\"", apkSource.getApkLength(), sessionId, apkSource.getApkName()), apkSource.openApkInputStream()));
val filePath = File(apkSource.parentFilePath, apkSource.fileName).absolutePath
val result = Shell.su("\"pm install-write -S ${apkSource.fileSize} $sessionId \"${apkSource.fileName}\"").add(SuFileInputStream(filePath)).exec()
Log.d("AppLog", "success pushing apk:${apkSource.fileName} ? ${result.isSuccess}")
}
But when I reach the line of "Shell.su", it gets stuck.
Attached here a sample project to show the issue. Also attached a bunch of split APK files that I got from Google News APKs.
splitApks.zip
splitApkInstallViaRoot.zip
Any idea what I'm doing wrong?
Is this the wrong syntax to use it?
"test" is not printed.
public class App extends Application {
static {
Shell.Config.setFlags(Shell.FLAG_REDIRECT_STDERR);
Shell.Config.verboseLogging(BuildConfig.DEBUG);
Shell.Config.addInitializers(BusyBoxInstaller.class);
}
...
}
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
...
Shell.su("echo 111").exec();
Log.d("test", "test");
...
}
}
When I try to create my own sample, to try this library, I get this error:
FAILURE: Build failed with an exception.
> Transform artifact io.aar (com.github.topjohnwu.libsu:io:2.5.0) with DexingTransform
AGPBI: {"kind":"error","text":"Default interface methods are only supported starting with Android N (--min-api 24): boolean com.topjohnwu.superuser.internal.DataInputImpl.readBoolean()","sources":[{}],"tool":"D8"}
> Transform artifact core.aar (com.github.topjohnwu.libsu:core:2.5.0) with DexingTransform
AGPBI: {"kind":"error","text":"Static interface methods are only supported starting with Android N (--min-api 24): void com.topjohnwu.superuser.internal.InputHandler.lambda$newInstance$0(java.lang.String[], java.io.OutputStream)","sources":[{}],"tool":"D8"}
> Task :app:transformClassesWithDexBuilderForDebug
> Task :app:validateSigningDebug
> Task :app:signingConfigWriterDebug
> Task :app:mergeExtDexDebug FAILED
FAILURE: Build failed with an exception.
* What went wrong:
Execution failed for task ':app:mergeExtDexDebug'.
> Could not resolve all files for configuration ':app:debugRuntimeClasspath'.
> Failed to transform artifact 'io.aar (com.github.topjohnwu.libsu:io:2.5.0)' to match attributes {artifactType=android-dex, dexing-is-debuggable=true, dexing-min-sdk=16}
> Execution failed for DexingTransform: C:\Users\user\.gradle\caches\transforms-2\files-2.1\ad09094c295507c71e0ea2562bafdd42\jars\classes.jar.
> Error while dexing.
> Failed to transform artifact 'core.aar (com.github.topjohnwu.libsu:core:2.5.0)' to match attributes {artifactType=android-dex, dexing-is-debuggable=true, dexing-min-sdk=16}
> Execution failed for DexingTransform: C:\Users\user\.gradle\caches\transforms-2\files-2.1\f59428331cb15ac5a034471a35c8bd68\jars\classes.jar.
> Error while dexing.
* Try:
Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights.
This is weird, because the sample of the repository requires Android API 16 and above, and it works
And I use a very simple code:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (savedInstanceState == null) {
Shell.Config.setFlags(Shell.FLAG_REDIRECT_STDERR);
Shell.Config.verboseLogging(BuildConfig.DEBUG);
Shell.Config.setTimeout(10);
}
AsyncTask.execute {
var result = Shell.su("ls /data/").exec()
Log.d("AppLog", "result: result.code:${result.code} result.isSuccess:${result.isSuccess}")
Log.d("AppLog", "result.out:${result.out.joinToString()}")
Log.d("AppLog", "result.err:${result.err.joinToString()}")
Thread.sleep(2000)
result = Shell.su("ls /data/").exec()
Log.d("AppLog", "result: result.code:${result.code} result.isSuccess:${result.isSuccess}")
Log.d("AppLog", "result.out:${result.out.joinToString()}")
Log.d("AppLog", "result.err:${result.err.joinToString()}")
}
}
}
Attached sample.
Hello John,
I tried few things with SuRandomAccessFile
and I find an issue with SuFile
.
Here is the code I tried which works fine with a File
:
try {
SuRandomAccessFile file = SuRandomAccessFile.open(new File("/system/etc/hosts"), "r");
String line;
while ((line = file.readLine()) != null) {
Log.e("TEST", line);
}
} catch (IOException e) {
Log.e("TEST", "error", e);
}
But if I use a SuFile
instead of a java File
:
try {
SuRandomAccessFile file = SuRandomAccessFile.open(new SuFile("/system/etc/hosts"), "r");
String line;
while ((line = file.readLine()) != null) {
Log.e("TEST", line);
}
} catch (IOException e) {
Log.e("TEST", "error", e);
}
I always got an error:
2020-08-16 11:44:24.736 21996-21996/com.topjohnwu.libsuexample D/LIBSU: java.io.InterruptedIOException
at com.topjohnwu.superuser.internal.ShellImpl$DefaultTask.run(ShellImpl.java:280)
at com.topjohnwu.superuser.internal.ShellImpl.execTask(ShellImpl.java:233)
at com.topjohnwu.superuser.internal.JobImpl.exec0(JobImpl.java:53)
at com.topjohnwu.superuser.internal.JobImpl.exec(JobImpl.java:70)
at com.topjohnwu.libsuexample.MainActivity$ExampleInitializer.onInit(MainActivity.java:84)
at com.topjohnwu.superuser.internal.BuilderImpl.build(BuilderImpl.java:84)
at com.topjohnwu.superuser.internal.BuilderImpl.build(BuilderImpl.java:58)
at com.topjohnwu.superuser.internal.MGR.getShell(MGR.java:41)
at com.topjohnwu.superuser.Shell.getShell(Shell.java:148)
at com.topjohnwu.superuser.ShellUtils.fastCmdResult(ShellUtils.java:85)
at com.topjohnwu.superuser.io.SuFile.cmdBool(SuFile.java:110)
at com.topjohnwu.superuser.io.SuFile.exists(SuFile.java:184)
at com.topjohnwu.superuser.internal.ShellIO.<init>(ShellIO.java:57)
at com.topjohnwu.superuser.internal.ShellIO.get(ShellIO.java:75)
at com.topjohnwu.superuser.internal.IOFactory.createShellIO(IOFactory.java:41)
at com.topjohnwu.superuser.io.SuRandomAccessFile.open(SuRandomAccessFile.java:54)
at com.topjohnwu.libsuexample.MainActivity.runIoTests(MainActivity.java:244)
at com.topjohnwu.libsuexample.MainActivity.lambda$onCreate$5$MainActivity(MainActivity.java:219)
at com.topjohnwu.libsuexample.-$$Lambda$MainActivity$a7zsI796bNp0TD7tk4yhXst0ak4.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7259)
at android.view.View.performClickInternal(View.java:7236)
at android.view.View.access$3600(View.java:801)
at android.view.View$PerformClick.run(View.java:27892)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.util.concurrent.ExecutionException: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
at java.util.concurrent.FutureTask.report(FutureTask.java:123)
at java.util.concurrent.FutureTask.get(FutureTask.java:193)
at com.topjohnwu.superuser.internal.ShellImpl$DefaultTask.run(ShellImpl.java:277)
at com.topjohnwu.superuser.internal.ShellImpl.execTask(ShellImpl.java:233)
at com.topjohnwu.superuser.internal.JobImpl.exec0(JobImpl.java:53)
at com.topjohnwu.superuser.internal.JobImpl.exec(JobImpl.java:70)
at com.topjohnwu.libsuexample.MainActivity$ExampleInitializer.onInit(MainActivity.java:84)
at com.topjohnwu.superuser.internal.BuilderImpl.build(BuilderImpl.java:84)
at com.topjohnwu.superuser.internal.BuilderImpl.build(BuilderImpl.java:58)
at com.topjohnwu.superuser.internal.MGR.getShell(MGR.java:41)
at com.topjohnwu.superuser.Shell.getShell(Shell.java:148)
at com.topjohnwu.superuser.ShellUtils.fastCmdResult(ShellUtils.java:85)
at com.topjohnwu.superuser.io.SuFile.cmdBool(SuFile.java:110)
at com.topjohnwu.superuser.io.SuFile.exists(SuFile.java:184)
at com.topjohnwu.superuser.internal.ShellIO.<init>(ShellIO.java:57)
at com.topjohnwu.superuser.internal.ShellIO.get(ShellIO.java:75)
at com.topjohnwu.superuser.internal.IOFactory.createShellIO(IOFactory.java:41)
at com.topjohnwu.superuser.io.SuRandomAccessFile.open(SuRandomAccessFile.java:54)
at com.topjohnwu.libsuexample.MainActivity.runIoTests(MainActivity.java:244)
at com.topjohnwu.libsuexample.MainActivity.lambda$onCreate$5$MainActivity(MainActivity.java:219)
at com.topjohnwu.libsuexample.-$$Lambda$MainActivity$a7zsI796bNp0TD7tk4yhXst0ak4.onClick(Unknown Source:2)
at android.view.View.performClick(View.java:7259)
at android.view.View.performClickInternal(View.java:7236)
at android.view.View.access$3600(View.java:801)
at android.view.View$PerformClick.run(View.java:27892)
at android.os.Handler.handleCallback(Handler.java:883)
at android.os.Handler.dispatchMessage(Handler.java:100)
at android.os.Looper.loop(Looper.java:214)
at android.app.ActivityThread.main(ActivityThread.java:7356)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:492)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:930)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.lang.String.length()' on a null object reference
at com.topjohnwu.superuser.internal.StreamGobbler.isEOS(StreamGobbler.java:46)
at com.topjohnwu.superuser.internal.StreamGobbler$OUT.call(StreamGobbler.java:77)
at com.topjohnwu.superuser.internal.StreamGobbler$OUT.call(StreamGobbler.java:66)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:919)
It comes from the StreamGobbler
which tries to find the EOS when the BufferedReader
reaches EOF first.
I think it does not find the eos
tag and isEOS
fails.
Moreover, there is something strange with the isEOS
implementation:
protected boolean isEOS(String line) {
boolean eof = false;
int sl = line.length() - 1;
int tl = eos.length() - 1;
if (sl >= tl) {
eof = true;
for (; tl >= 0; --tl, --sl) {
if (eos.charAt(tl) != line.charAt(sl)) {
eof = false;
break;
}
}
if (eof)
line = sl >= 0 ? line.substring(0, sl + 1) : null;
}
if (list != null && line != null) {
list.add(line);
Utils.log(TAG, line);
}
return eof;
}
line
is never tested to be null
at method start where it can becomes null
and nullability is checked later.
Long story short: I might be using SuFile
the wrong way. If not, there might be a bug with SuRandomAccessFile
Regards,
Bruce
Hi,
I'm the author of Raw Dumper (camera app for taking raw pictures on x86 phones, like the Zenfone 2)
and I'm planning to replace libsuperuser with libsu.
I have a really specific use case: during the app lifetime, I need to keep parsing the massive output of a system-level logcat command; in realtime, without allocating String objects.
Overriding the Task interface and calling execTask would suit my needs?
PS: The whole logcat reading stuff is needed to get some raw sensor AWB gains to generate raw pics with pretty colors and proper white balance.
Hi,
I'm trying to get the lastModified of a file wrapped around SUFile but the lastModified()
method always returns 0 to me. It returns correct value for userspace files though using the Java's File implementation. I will be able to dig up some logs or test cases if you need anything more
Is there a Wiki that shows all that's available?
Is there, for example, a way to convert apps to system apps and back? Is BusyBox required for it?
Some su
will create garbage output (warnings) before running the actual shell. This will trigger IOException("Created process is not a shell")
in ShellImpl
. It would be great to ignore those errors.
Great job and thanks for your work!
I use this lib to call a external program, but sometimes I want to close this externally called program, what should I do? I tried before:
shell.exec () + shell.close ()
but sometimes external programs it didn't stop running, while most of the time it stopped, how should I call and exit the process, can you help me.
Looks like testCmd doesn't check for a non null result returned from fastCmd before using split()
FATAL EXCEPTION: main
Process: com.topjohnwu.magisk, PID: 23379
java.lang.ExceptionInInitializerError
at com.topjohnwu.magisk.database.SuDatabaseHelper.openDatabase(Unknown Source)
at com.topjohnwu.magisk.database.SuDatabaseHelper.(Unknown Source)
at com.topjohnwu.magisk.database.SuDatabaseHelper.getInstance(Unknown Source)
at com.topjohnwu.magisk.MagiskManager.onCreate(Unknown Source)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1036)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6321)
at android.app.ActivityThread.access$1800(ActivityThread.java:222)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1861)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7229)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'java.lang.String[] java.lang.String.split(java.lang.String)' on a null object reference
at com.topjohnwu.superuser.internal.ShellImpl.testCmd(Unknown Source)
at com.topjohnwu.superuser.io.SuFile.testShell(Unknown Source)
at com.topjohnwu.superuser.io.SuFile.(Unknown Source)
at com.topjohnwu.magisk.utils.Const.(Unknown Source)
at com.topjohnwu.magisk.database.SuDatabaseHelper.openDatabase(Unknown Source)
at com.topjohnwu.magisk.database.SuDatabaseHelper.(Unknown Source)
at com.topjohnwu.magisk.database.SuDatabaseHelper.getInstance(Unknown Source)
at com.topjohnwu.magisk.MagiskManager.onCreate(Unknown Source)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1036)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6321)
at android.app.ActivityThread.access$1800(ActivityThread.java:222)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1861)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:158)
at android.app.ActivityThread.main(ActivityThread.java:7229)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)
This is more of a question than an issue.
I'd like to use this library, but before I do so, I'd like to know if this library has any implicit dependendies. For example, does the library depend on Magisk being used? Will it work with ChainfireSU or any other traditional su
setup?
In the application, I have the option to run as root, when the user sets root exec Shell.su(...)
or other Shell.sh(...) but this does not work, and the file runs as root in both cases
hi mr topjohnwu,
there is some apps still can launched but its should not detecting root. can help me to add this apk to libsu? i already using magisk hide but its still detecting root.
here I attach the application name
thanks before mr.topjohnwu.
Hi,
it seems that busybox (or at least mkfs.vfat applet) is compiled without Large File Support, so if I try to format a file created with truncate that is bigger than 2GB I get the following error:
mkfs.vfat: lseek: Value too large for defined data type
Also, if I use fdisk on a file that is bigger than 2GB I get this message:
Unknown value(s) for: cylinders (settable in the extra functions menu)
I don't know if that too relates to LFS, I just set the value manually in this case.
Would you maybe add the LFS flag when compiling busybox?
Thanks and great project, btw!
Hi, I just called Shell.sh(cmd).exec().out
, and cmd
is a normal command like ls
that do not need root access. But the root dialog prompted on rooted device.
Any solutions to avoid this? Thx. :)
Meaning @workerthread , @UiThread , and@AnyThread
我的手機Android 8.1.0,使用TWRP3.2.1.0刷入Magisk(v16.0).zip(正常刷入,無錯誤提示),重啟後打開Magisk Manager app,發現除了安裝狀態變成v16.0外,其他功能都沒有,就像沒成功刷入Magisk(v16.0).zip一樣。
ShellUtils.fastCmd can return null. At the moment this causes its caller to crash.
In order to avoid a java.lang.NullPointerException, either this code needs to check for such a null return value, or ShellUtils.fastCmd should be modified so it always returns a valid string (never null), or ShellUtils.fastCmd should raise an exception instead of returning null when it encounters a fatal error.
This simple code:
thread {
val result = Shell.su("ls -l \"/data/data/\"").exec().out
Log.d("AppLog", "resulted lines of querying files on folder:${result.size}")
}
implementation "com.github.topjohnwu.libsu:core:2.5.1"
When I target API 29, it seems all fine.
When I target API 30, it has about 3 rows as result...
iGHQDEjNdU.zip
My Application.zip
Is it possible it's one of the restrictions on Android API 30?
But using adb via USB, it works fine...
How can I overcome this?
Simple code to replicate:
val SCRIPT_FILENAME: String = "/cache/recovery/openrecoveryscript"
val scriptFile = SuFile(SCRIPT_FILENAME)
val logFiles = scriptFile.parentFile.listFiles { _, name -> name.endsWith(".log", true) }
v1.2.0 works ok, v1.3.0 throws NullPointerException
UPD: folder you are listing must be empty
The IO library does not work for pseudo-files.
Here's normal non-root Java code that works:
> FileUtils.readFileToString(new File("/proc/cpuinfo"))
Processor : AArch64 Processor rev 2 (aarch64)
processor : 0
BogoMIPS : 38.00
...
However, when you try to do the same with libsu's IO library, it always returns empty.
The example file chosen here (/proc/cpuinfo
) doesn't even need root privileges.
I'm not too familiar with Java. However, If you can point me to the faulty code, I can take a shot at fixing it.
I'm using SuFile wrapper class to read the files in non-userspace directories and I'm able to retrieve list for all except for the root "/" directory. It throws NumberFormatException when try to use length() on the SuFile. It works fine when I don't use the length() method. I was able to navigate upto /storage but on / I get an FC with the above exception.
Is it known issue?
Hi,
I'm working on an app that extracts data from an tar file using apache commons' implementation for that.
I'm now stuck for days because there is a nasty issue on the suFile implementation and maybe someone can tell what's going on:
So this is the log output:
Note: I overwrote the filepath manually to see, if it's related to the filename, but here you can see, it isn't.
// … next file
D/SHELL_IN: __F_='/data/user/0/com.example.org/app_webview/Local Storage/leveldb/000016.ldb'
[ -b "$__F_" ]
D/SHELL_IN: __F_='/data/user/0/com.example.org/app_webview/Local Storage/leveldb/000016.ldb'
[ -d "$__F_" ]
D/SHELL_IN: __F_='/data/user/0/com.example.org/app_webview/Local Storage/leveldb/000016.ldb'
echo -n > "$__F_"
D/SHELLIO: dd bs=88093 count=1 >> '/data/user/0/com.example.org/app_webview/Local Storage/leveldb/000016.ldb' 2>/dev/null; echo
// … next file
D/SHELL_IN: __F_='/data/user/0/com.example.org/app_webview/Local Storage/leveldb/000016.ldb'
[ -b "$__F_" ]
According to the log, it's stuck on the [ -b "$__F" ]
command that checks if my (not existing, but it doesn't matter) file is a block device.
Due to the fact, that I'm extracting a bunch of files, I think, it's related to the high frequency of commands send to my shell instance.
Here's a short code snippet, to better understand, what I'm doing:
TarArchiveEntry tarEntry;
while ((tarEntry = archive.getNextTarEntry()) != null) {
if (tarEntry.isFile()) {
final File targetFile = new File(targetDir, tarEntry.getName());
//try (SuFileOutputStream fos = new SuFileOutputStream(SuFile.open(targetDir, tarEntry.getName()))) {
String name = tarEntry.getName();
if(name.contains("000014"))
name = "app_webview/Local Storage/leveldb/000016.ldb";
try (SuFileOutputStream fos = new SuFileOutputStream(SuFile.open(targetDir, name))) {
IOUtils.copy(archive, fos, TarUtils.BUFFERSIZE);
}
}
When I insert a `Thread.sleep(1000);) in the loop, it works. 100ms is not enough.
Tested on Android API 27 x86 Emulator
Funny enough, when I used the File type as parameter, the execution stopped in this constructor:
SuFile(@NonNull File file) {
super(file.getAbsolutePath());
CMDs = new String[2];
CMDs[0] = "__F_='" + file.getAbsolutePath() + "'";
}
On the line CMDs[0] = "__F_='" + file.getAbsolutePath() + "'";
and sometimes the app gets killed by the system server because Out of Memory issues. During execution there were 600 MB available…
Very weird. I cannot see anything wrong on the string.
If I want to list a big folders's files (~14k files), most of the time goes by creating the (unused) stack trace for an exception which is just used for returning a true if occurred.
If the process is still running while executing ProcessImpl.exitValue() (and most of the times it does), java.lang.Throwable.nativeFillInStackTrace
can take from 1,5 ms to 66,7 ms. In a loop of 14k files, this way of checking if the shell process vanished wastes ~80% of the time after we got the response.
ProcessImpl.isAlive() is not supported below SDK 26, but ProcessImpl.exitValue can still be checked via reflection. You could first check if that field exists (since there could be roms which don't have it), and if not, you could still use the old approach, and from SDK 26 you could use isAlive()
Hi
Goal is to modify an Xml file in root. For this test, I didn't change any Xml file content at all, just read by stream and write by stream. So.
When I use SuFileOutputStream and then "ShellUtils.pump" or use OutputStreamWriter(SuFileOutputStream) to write the original file content back to an Xml file.
Writing file content is successful, but after the Xml file rewritten, original App which need to access that Xml file has lost it's ability to do so. Is there any obvious reason why?
Workaround I found is using "Shell.Sync.sh( list,"cp -r src des")" copy the Xml file out of root to SDCard, and then modify, then using same command to copy file back to root. This way the app can still access this Xml file. However, I've read command cp is not widely supported by phones, does libsu library solved this problem? Is this command safe to use for all phones?
A few tests I did.
First thought was permission blocked the app to access that Xml file, so I set that Xml file permission to rw-rw-rw-, even rwxrwxrwx, but the app still can't read that rewritten Xml file.
I can't use RandomAccessFile to modify part of file without rewriting, because this doesn't use stream, so can't get root permission.
I use EsExplorer to check what changes happened to the file after rewritten.
Original file permission, rw-rw----, after rewritten is rw-r--r--, libsu library doesn't have functions to change permissions separately, so I used EsExplorer to change the permission back to rw-rw----, still no luck.
Original file Owner is blank, Group is blank. After rewritten, Owner is root, Group is root. libsu library and EsExplorer both don't have anyway to change those back to blank. So I couldn't test this.
Any help is appreciated, thanks a lot.
I don't see it mentioned on the main page of the repo....
Under the hood of Shell.newInstance, it delegates the creation of new shell instance to the main thread. This could be problematic if the main thread is waiting for it to finish (e.g. using a synchronized/monitor to guard around the root shell). Should this be fixed?
HI my safetynet does not work its shows false in my superman rom
How i can fix it
Currently, we have to use something like that in case we were denied from getting root, and later got root manually from the root app:
val gotRoot = Shell.getCachedShell()?.isRoot == true || Shell.newInstance().isRoot
That's because Shell.getShell()
tries to gain root only once, no matter how many times we call it.
``
is there an NDK equivilant for C++ use?
Hi,
Great library first of all. I'm the developer of Swift Backup and I recently replaced ChainFire's libsuperuser library to Magisk libsu.
Some testers are still using SuperSU on older devices and Shell.getShell() blocks the thread indefinitely when root is requested and user denies access from the dialog.
This is how I request root access:
public static boolean isAvailable() {
Log.d(TAG, "isAvailable: Checking");
Const.checkBackgroundThread();
IS_ROOTED = Shell.rootAccess();
return IS_ROOTED;
}
The method never returns the boolean when user denies root access from the request dialog.
Error logs:
2018-07-19 14:46:29.281 22307-22375/org.swiftapps.swiftbackup D/LIBSU: Internal Error
java.io.IOException
at com.topjohnwu.superuser.internal.ShellImpl.<init>(ShellImpl.java:117)
at com.topjohnwu.superuser.internal.Factory.createShell(Factory.java:26)
at com.topjohnwu.superuser.Shell.newInstance(Shell.java:329)
at com.topjohnwu.superuser.Shell.getShell(Shell.java:261)
at com.topjohnwu.superuser.Shell.rootAccess(Shell.java:380)
at org.swiftapps.swiftbackup.common.RootHelper.isAccessGiven(RootHelper.java:48)
at org.swiftapps.swiftbackup.common.RootHelper.lambda$isAccessGiven$3(RootHelper.java:55)
at org.swiftapps.swiftbackup.common.-$$Lambda$RootHelper$IFLv8vCPCpKarMRFhE5wv-2MfFs.run(Unknown Source:2)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)
at java.lang.Thread.run(Thread.java:764)
2018-07-19 14:46:29.281 22307-22375/org.swiftapps.swiftbackup D/SHELLIMPL: exec sh
2018-07-19 14:46:29.286 22307-22375/org.swiftapps.swiftbackup D/SHELLIMPL: token: xr85KxwdgoIKRuccWDboKiWO7WbdYTwB
By using your library, I ran into the following issues.
Hello. I got the error when Magisk Manager start.
Supported applets:
06-26 10:20:53.797 10206-10287/com.topjohnwu.magisk D/SHELLOUT: su, resetprop, magiskhide, imgtool
06-26 10:20:53.798 10206-10287/com.topjohnwu.magisk D/SHELLOUT: sh: <stdin>[457]: }
: not found
sh: <stdin>[458]:
: not found
sh: <stdin>[461]:
/ 100000: unexpected '/'
06-26 10:20:53.800 10206-10206/com.topjohnwu.magisk D/SHELLIMPL: runSyncTask
runCommands
06-26 10:20:53.802 10206-10206/com.topjohnwu.magisk D/LIBSU: Internal Error
java.io.IOException: write failed: EPIPE (Broken pipe)
at libcore.io.IoBridge.write(IoBridge.java:498)
at java.io.FileOutputStream.write(FileOutputStream.java:186)
at com.topjohnwu.superuser.internal.ShellImpl$NoCloseOutputStream.write(ShellImpl.java:78)
at java.io.OutputStream.write(OutputStream.java:82)
at com.topjohnwu.superuser.internal.ShellImpl.lambda$createCmdTask$5(ShellImpl.java:239)
at com.topjohnwu.superuser.internal.ShellImpl$$Lambda$3.run(ShellImpl.java)
at com.topjohnwu.superuser.internal.ShellImpl.lambda$execSyncTask$0(ShellImpl.java:196)
at com.topjohnwu.superuser.internal.ShellImpl$$Lambda$1.run(ShellImpl.java)
at com.topjohnwu.superuser.internal.ShellImpl.execTask(ShellImpl.java:177)
at com.topjohnwu.superuser.internal.ShellImpl.execSyncTask(ShellImpl.java:192)
at com.topjohnwu.superuser.Shell.run(Shell.java:733)
at com.topjohnwu.magisk.utils.ShellInitializer.onRootShellInit(ShellInitializer.java:24)
at com.topjohnwu.superuser.Shell$Initializer.init(Shell.java:979)
at com.topjohnwu.superuser.Shell$Initializer.access$200(Shell.java:928)
at com.topjohnwu.superuser.Shell.initShell(Shell.java:807)
at com.topjohnwu.superuser.Shell.newInstance(Shell.java:350)
at com.topjohnwu.superuser.Shell.getShell(Shell.java:261)
at com.topjohnwu.superuser.ShellUtils.fastCmd(ShellUtils.java:91)
at com.topjohnwu.magisk.MagiskManager.loadMagiskInfo(MagiskManager.java:190)
at com.topjohnwu.magisk.database.MagiskDatabaseHelper.openDatabase(MagiskDatabaseHelper.java:75)
at com.topjohnwu.magisk.database.MagiskDatabaseHelper.<init>(MagiskDatabaseHelper.java:54)
at com.topjohnwu.magisk.database.MagiskDatabaseHelper.getInstance(MagiskDatabaseHelper.java:44)
at com.topjohnwu.magisk.MagiskManager.onCreate(MagiskManager.java:116)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1013)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4707)
at android.app.ActivityThread.-wrap1(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1405)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
Caused by: android.system.ErrnoException: write failed: EPIPE (Broken pipe)
at libcore.io.Posix.writeBytes(Native Method)
at libcore.io.Posix.write(Posix.java:271)
at libcore.io.BlockGuardOs.write(BlockGuardOs.java:313)
at libcore.io.IoBridge.write(IoBridge.java:493)
at java.io.FileOutputStream.write(FileOutputStream.java:186)
at com.topjohnwu.superuser.internal.ShellImpl$NoCloseOutputStream.write(ShellImpl.java:78)
at java.io.OutputStream.write(OutputStream.java:82)
at com.topjohnwu.superuser.internal.ShellImpl.lambda$createCmdTask$5(ShellImpl.java:239)
at com.topjohnwu.superuser.internal.ShellImpl$$Lambda$3.run(ShellImpl.java)
at com.topjohnwu.superuser.internal.ShellImpl.lambda$execSyncTask$0(ShellImpl.java:196)
at com.topjohnwu.superuser.internal.ShellImpl$$Lambda$1.run(ShellImpl.java)
at com.topjohnwu.superuser.internal.ShellImpl.execTask(ShellImpl.java:177)
at com.topjohnwu.superuser.internal.ShellImpl.execSyncTask(ShellImpl.java:192)
at com.topjohnwu.superuser.Shell.run(Shell.java:733)
at com.topjohnwu.magisk.utils.ShellInitializer.onRootShellInit(ShellInitializer.java:24)
at com.topjohnwu.superuser.Shell$Initializer.init(Shell.java:979)
at com.topjohnwu.superuser.Shell$Initializer.access$200(Shell.java:928)
at com.topjohnwu.superuser.Shell.initShell(Shell.java:807)
at com.topjohnwu.superuser.Shell.newInstance(Shell.java:350)
at com.topjohnwu.superuser.Shell.getShell(Shell.java:261)
at com.topjohnwu.superuser.ShellUtils.fastCmd(ShellUtils.java:91)
at com.topjohnwu.magisk.MagiskManager.loadMagiskInfo(MagiskManager.java:190)
at com.topjohnwu.magisk.database.MagiskDatabaseHelper.openDatabase(MagiskDatabaseHelper.java:75)
at com.topjohnwu.magisk.database.MagiskDatabaseHelper.<init>(MagiskDatabaseHelper.java:54)
at com.topjohnwu.magisk.database.MagiskDatabaseHelper.getInstance(MagiskDatabaseHelper.java:44)
at com.topjohnwu.magisk.MagiskManager.onCreate(MagiskManager.java:116)
at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1013)
at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4707)
at android.app.ActivityThread.-wrap1(ActivityThread.java)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1405)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:148)
at android.app.ActivityThread.main(ActivityThread.java:5417)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)
06-26 10:20:53.802 10206-10206/com.topjohnwu.magisk D/SHELLIMPL: close
06-26 10:20:53.803 10206-10206/com.topjohnwu.magisk D/SHELLIMPL: exec su --mount-master
The current implementation ignores all pending tasks and kills the shell process. Is there any way to wait for all the tasks to finish?
Hi @topjohnwu
First of all, thanks for your hard work!
Building a library of this kind is not easy and require a lot of time.
As you ask, I posted here some thoughts about the library. I studied it when release (so before you left for your military training) and I had some concerns by the time. I did not want to bother you with it.
But I am happy to see it received a lot of improvements since! 😄
I am the maintainer of AdAdaway, a root adblocker (https://github.com/AdAway/AdAway) and I had a quick look to see I could migrate from RootCommands (https://github.com/Free-Software-for-Android/RootCommands) to your library (I maintain a fork as a module of AdAway).
So for my needs, I see some interesting additions:
But compared to RootCommands, there is no concept of command itself: all commands are strings. It was useful to have it: to set parameter, chain them, get error code, etc…
So I read through the code of latest version and I have few questions and remarks.
Keep in mind I am not here to point some bad things in the code, just to start a discussion about the question you ask me 😉
In my opinion Shell.Sync
and Shell.Async
methods looks weird.
First, varargs and List are mixed. Then required parameter, commands
, is not at the same place for each sh
, su
, loadScript
variant. It could be the first parameter so it will be consistent to every other overloaded methods.
The output parameters, output
and error
lists, deserved a dedicated structure as a return type.
Doing so, List<String> su(String...)
, su(List<String>, String...)
and su(List<String>, List<String>, String...)
could be merged into only one method like Output su(String...)
where Output
contains twos List<String>
, output
and error
, which may be empty.
Creating an Output
class could also lead to move String cleanup like ShellUtils.isValidOutput
to this class.
Example: output or error list merged in one line, get the last line, etc...
And what about error code? Token is already used to detect the end of a command. It could be also printed and retrieved like it is done in boolean ShellUtils.fastCmdResult(Shell shell, String cmd)
.
About Shell.Initializer
, onShellInit
and onRootShellInit
could be the same.
Just look at the given shell in parameter to check if it is root or not (with getStatus()
for example or a dedicated function like isRoot()
).
Once cleaned, we could have a functional interface as shell initializer (BiPredicate<Context, Shell>
).
Same from throwing an exception, the documentation said "it is the same as returning false". As duplicate, it could be removed.
Same about Shell.Task
, is throwing Exception redundant with stderr? Thrown exceptions in task look like to be related to I/O internals and implementation details. May be the user should not be aware of this kind of error and the exception message should be added to stderr instead? (while stack trace could stil be logged) Because if the user (ie the app developer) can't do anything about this exception, it seems the library should in charge of handling it.
And, at last, it is usually recommended to return empty collections or arrays instead of null
. See Item 54 of Josh Bloch's Effective Java. About the "all the commands are String", I would recommend to read the Item 62 (Avoid String where other types are more appropriate), and "varargs and list", check Item 53 (Use varargs judiciously) because the API allow the call Shell.Sync.su()
(without a command). Even if it should work, do we really want it?
I still have a lot of questions when I start reading the implementation details, specially about threading, locking, input/output stream encoding, etc.., but it is already late here and I guess this issue is already long enough! Anyway, thanks for your work! 👍
Trying to build a project in AS with a dependency on libsu 2.3.0, I get errors relating to 'common invoke' and 'static methods in interfaces', which are apparently only available in SDK 26+. Samsung haven't upgraded their stock since 6.0.1 for me, which means I'm stuck at SDK 23, so can't go beyond libsu 2.2.0
Any chance of removing these weird Java/SDK features, or can android M no longer be supported ?
I'm writing an app that wants to make use of root if it is available. The way Android runtime permissions were designed was to allow app developers to explain how they use a permission before requesting it.
Currently, the only way I can get the su permission grant dialog to show up is to try and use root privileges. Is there a way to test whether root access is available before using (implicitly triggering check) it.
I'm struggling with coming up with a way to:
Hey! Great work with this library, I checked the docs, but could not find a method to remount any partition(like /system) to rw or other which is available in the roottools library. Please instruct me on how to do it other than using the shell commands or if this functionality is missing, you might consider adding it...
Hi @topjohnwu.
Great job and thanks for your work!
I tried the lib but while executing line commands everything is working fine when I try to execute scripts I found that the script never executes.
I was able to find that the problem is in the exit. Whenever I use exit (also in commands) the lib always returns code -1. I tried to substitute the exit by return but I always get the code -1.
I tried a sync call:
Shell.Result result = Shell.su(getResources().openRawResource(resId)).exec();
mTvCode.setText("Code: " + result.getCode());
List out = result.getOut();
StringBuilder sbOut = new StringBuilder();
if (out != null) {
for (String o : out) {
sbOut.append(o);
}
}
mTvOutput.setText("Out: " + sbOut.toString());
List err = result.getErr();
StringBuilder sbErr = new StringBuilder();
if (err != null) {
for (String e : err) {
sbErr.append(e);
}
}
mTvErr.setText("Err: " + sbErr.toString());
And I tried async call:
Shell.su(getResources().openRawResource(R.raw.script))
.to(consoleList)
.submit(result -> {
mTvCode.setText("Code: " + result.getCode());
List out = result.getOut();
StringBuilder sbOut = new StringBuilder();
if (out != null) {
for (String o : out) {
sbOut.append(o);
}
}
mTvOutput.setText("Out: " + sbOut.toString());
List err = result.getErr();
StringBuilder sbErr = new StringBuilder();
if (err != null) {
for (String e : err) {
sbErr.append(e);
}
}
mTvErr.setText("Err: " + sbErr.toString());
});
The result is always the same.
Here's one script example:
#!/system/bin/sh
function exit_error(){
case "$1" in
1)
echo 'busybox is not installed'
;;
esac
#exit 2
return 2
}busybox > /dev/null || exit_error 1
if [[ ! -f /sdcard/file.txt ]]; then
echo 'File not exists'
#exit 1
return 1
fiecho 'Exiting'
#exit 0
return 0`
Am I doing something wrong or is this a limitation?
Thanks in advance.
JS
i am trying to add import the library into Andorid Studio, but i get an error.
Failed to resolve: com.github.topjohnwu:libsu:2.0.0
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.