Code Monkey home page Code Monkey logo

libsu's People

Contributors

canyie avatar fox2code avatar kdrag0n avatar mygod avatar perfectslayer avatar tiann avatar topjohnwu avatar uditkarode avatar vvb2060 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

libsu's Issues

Realtime massive shell output reading

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.

how to quit a long-run shell program

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.

Shell.sh exec as root

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

file lastModified() always returns 0 (01-01-1970 00:00)

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

Request: add function to retry getting root

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.

ANR caused by deadlock in Shell.newInstance

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?

Cannot read pseudo-files with IO

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.

How to "push forward" from inputStream into shell?

Hello,
I'm trying to use this library to install split-APK files, as this one does:

https://github.com/Aefyr/SAI/blob/master/app/src/main/java/com/aefyr/sai/installer/rooted/RootedSAIPackageInstaller.java

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?

Ignore garbage output when checking for a shell?

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.

Can I use it without prompting root grant dialog?

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. :)

Error in import

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

Magisk v16.0

HI my safetynet does not work its shows false in my superman rom
How i can fix it

Possible bug: "Unable to create a shell!"

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.

suFile gets stuck

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.

Checking for root

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:

  1. Check if root is available
  2. Show dialog explaining how app uses root
  3. Trigger root app's grant/deny dialog

v1.3.0 NPE when trying to get folder listing

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

Improvenents with SuFile / IO

By using your library, I ran into the following issues.

  1. SuFile.delete() does not use root to delete files.
  2. If issue 1 is fixed, the system partition is still often mounted ro instead of rw, some method of remounting would be nice.

Is there a Wiki?

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?

Bug: targeting API 30, getting content of "/data/data/" returns a very small number of folders

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?

Unable to create my own sample: "Could not resolve all files for configuration ':app:debugRuntimeClasspath'."

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.

MyApplication.zip

Issue with SuRandomAccessFile and SuFile

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

Shell.su("echo 111").exec() blocked on abi aarch64

"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");
           ...
    }
}

NumberFormatException in SuFile length()

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?

ProcessImpl.exitValue() can take a lot of time to complete

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()

cant hide root detection

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

  1. com.gojek.driver.car or gocar driver.apk
  2. com.gojek.driver.bike or gojek driver.apk
  3. com.grabtaxi.driver2 or grab driver.apk
    this app its same as com.ubercab.driver.
    I using mi tissot with pass safetynet.

screenshot_2018-03-03-15-25-06-083_com topjohnwu magisk
screenshot_2018-03-03-15-25-22-336_com topjohnwu magisk
screenshot_2018-03-03-15-25-39-215_com topjohnwu magisk

thanks before mr.topjohnwu.

Missing Javadoc in gradle dependency

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

kep

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?

Executing scripts with exit or return code

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
fi

echo 'Exiting'
#exit 0
return 0`

Am I doing something wrong or is this a limitation?

Thanks in advance.
JS

Unknown issue of modify a file in root, and app can't access that file.

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.

D/LIBSU: Internal Error java.io.IOException: write failed: EPIPE (Broken pipe)

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

Missing Large File Support (CONFIG_LFS) in Busybox

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!

NullPointerException in ShellImpl.testCmd

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) 

libsu 2.3.0 no longer seems to support Android 6.0.1

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 ?

API Discussion

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:

  • I could create dedicated shells for long running tasks (I run web server and tcpdump processes in background) without blocking other shell commands,
  • I seems I can have a working shell without root (if a device is not rooted, I can still provide some features without crashing my app),
  • Busybox in included so device support should be good enough.

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! 👍

Read output/error as a stream?

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.

Indefinite blocking of thread when denied root permission from SuperSU dialog

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

Implicit Dependencies

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?

Method to remount target

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...

Close ShellImpl gracefully?

The current implementation ignores all pending tasks and kills the shell process. Is there any way to wait for all the tasks to finish?

Need to check for null return, or avoid it somehow

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.

String paths = ShellUtils.fastCmd(this, "echo $PATH");

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.