Skip to content

Commit

Permalink
Fixed crash on unavailable su binary.
Browse files Browse the repository at this point in the history
Added additional unit tests.
  • Loading branch information
d4rken committed Mar 17, 2018
1 parent e561243 commit f983a5f
Show file tree
Hide file tree
Showing 4 changed files with 41 additions and 32 deletions.
1 change: 1 addition & 0 deletions publish-to-bintray.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ bintray {

dryRun = false
publish = true
override = false

userOrg = user
licenses = bintrayConfig.allLicenses
Expand Down
23 changes: 11 additions & 12 deletions root/src/main/java/eu/darken/rxshell/root/Root.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import android.support.annotation.Nullable;

import java.io.IOException;
import java.util.Collection;
import java.util.Locale;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -103,20 +104,21 @@ public Single<Root> build() {
if (builder == null) builder = new RxCmdShell.Builder();
builder = builder.root(true);

final Cmd cmd = Cmd.builder("id").timeout(timeout).build();
RxCmdShell.Session session = null;
Cmd.Result result;
try {
session = builder.build().open().timeout(timeout, TimeUnit.MILLISECONDS).blockingGet();
result = session.submit(cmd).blockingGet();
} catch (RuntimeException e) {
if (e.getCause() instanceof TimeoutException) {
Timber.tag(TAG).w("Root check timed out after %dms", timeout);
try {
session = builder.build().open().timeout(timeout, TimeUnit.MILLISECONDS).blockingGet();
} catch (RuntimeException e) {
if (e.getCause() instanceof TimeoutException) {
Timber.tag(TAG).w("Waiting for su shell to open timed out after %dms", timeout);
} else if (e.getCause() instanceof IOException) {
Timber.tag(TAG).d("IOException when launching shell, likely no su binary!");
}
emitter.onSuccess(new Root(State.UNAVAILABLE));
return;
} else {
throw e;
}
result = Cmd.builder("id").timeout(timeout).execute(session);
} finally {
RxCmdShellHelper.blockingClose(session);
}
Expand All @@ -134,12 +136,9 @@ public Single<Root> build() {
rootState = State.ROOTED;
}
}
} else if (result.getExitCode() == Cmd.ExitCode.EXCEPTION) {
// There was likely no su binary at all
Timber.tag(TAG).d("IOException when launching shell, no su binary?");
} else if (result.getExitCode() == Cmd.ExitCode.PROBLEM || result.getExitCode() == Cmd.ExitCode.SHELL_DIED || result.getExitCode() == Cmd.ExitCode.TIMEOUT) {
// Either we were denied root or there was an error with one of the commands, lets switch up
Cmd.Result secondTry = Cmd.builder("echo test > /cache/root_test.tmp").timeout(20 * 1000).execute(builder.build());
Cmd.Result secondTry = Cmd.builder("echo test > /cache/root_test.tmp").timeout(timeout).execute(builder.build());
rootState = secondTry.getExitCode() == Cmd.ExitCode.OK ? State.ROOTED : State.DENIED;
if (rootState == State.ROOTED) Timber.tag(TAG).d("We got ROOT on second try :o ?");
}
Expand Down
2 changes: 1 addition & 1 deletion root/src/main/java/eu/darken/rxshell/root/SuApp.java
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ public Single<SuApp> build(SuBinary suBinary) {
String apkPath = null;

if (type == SuBinary.Type.UNKNOWN || type == SuBinary.Type.NONE) {
Timber.tag(TAG).i("Unknown SuBinary, can't determine SuApp.");
Timber.tag(TAG).d("Unknown or non existent su binary. Can't determine SuApp.");
} else {
String[] suAppPackages = SUAPPS.get(type);
PackageInfo pkgInfo = null;
Expand Down
47 changes: 28 additions & 19 deletions root/src/test/java/eu/darken/rxshell/root/RootTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.concurrent.TimeUnit;
Expand Down Expand Up @@ -44,23 +45,35 @@ public void setup() throws Exception {
}

@Test
public void testUnrooted() {
when(suBinary.getType()).thenReturn(SuBinary.Type.NONE);
final Root.Builder builder = new Root.Builder();
public void testOpen_no_binary() {
when(shell.open()).thenReturn(Single.error(new IOException()));

Root.Builder builder = new Root.Builder();
assertThat(builder.shellBuilder(shellBuilder).suBinary(suBinary).build().blockingGet().getState(), is(Root.State.UNAVAILABLE));
}

@Test
public void testOpen_timeout() {
when(shellSession.submit(any(Cmd.class))).thenAnswer(invocation -> {
Cmd cmd = invocation.getArgument(0);
if (cmd.getCommands().contains("id")) {
return Single.just(new Cmd.Result(cmd, Cmd.ExitCode.EXCEPTION));
return Single.just(new Cmd.Result(cmd, Cmd.ExitCode.OK, Collections.singletonList("uid=0(root) gid=0(root) groups=0(root) context=u:r:supersu:s0"), new ArrayList<>()));
} else return Single.error(new Exception("Unexpected state"));
});
when(shell.open()).thenReturn(Single.just(shellSession).delay(2000, TimeUnit.MILLISECONDS));

final Root root = builder.shellBuilder(shellBuilder).suBinary(suBinary).build().blockingGet();
assertThat(root.getState(), is(Root.State.UNAVAILABLE));
Root.Builder builder = new Root.Builder();
assertThat(builder.shellBuilder(shellBuilder).suBinary(suBinary).build().blockingGet().getState(), is(Root.State.ROOTED));

builder = new Root.Builder().timeout(1000);
assertThat(builder.shellBuilder(shellBuilder).suBinary(suBinary).build().blockingGet().getState(), is(Root.State.UNAVAILABLE));

builder = new Root.Builder();
assertThat(builder.shellBuilder(shellBuilder).suBinary(suBinary).build().blockingGet().getState(), is(Root.State.ROOTED));
}

@Test
public void testRooted() {
public void testCommand_rooted() {
when(shellSession.submit(any(Cmd.class))).thenAnswer(invocation -> {
Cmd cmd = invocation.getArgument(0);
if (cmd.getCommands().contains("id")) {
Expand All @@ -77,21 +90,17 @@ public void testRooted() {
}

@Test
public void testRooted_timeout() {
public void testCommand_timeout() {
when(shell.open()).thenReturn(Single.just(shellSession));

when(shellSession.submit(any(Cmd.class))).thenAnswer(invocation -> Single.just(new Cmd.Result(invocation.getArgument(0), Cmd.ExitCode.TIMEOUT, new ArrayList<>(), new ArrayList<>())));
Root.Builder builder = new Root.Builder().timeout(1000);
assertThat(builder.shellBuilder(shellBuilder).suBinary(suBinary).build().blockingGet().getState(), is(Root.State.DENIED));

when(shellSession.submit(any(Cmd.class))).thenAnswer(invocation -> {
Cmd cmd = invocation.getArgument(0);
if (cmd.getCommands().contains("id")) {
return Single.just(new Cmd.Result(cmd, Cmd.ExitCode.OK, Collections.singletonList("uid=0(root) gid=0(root) groups=0(root) context=u:r:supersu:s0"), new ArrayList<>()));
} else return Single.error(new Exception("Unexpected state"));
return Single.just(new Cmd.Result(cmd, Cmd.ExitCode.OK, Collections.singletonList("uid=0(root) gid=0(root) groups=0(root) context=u:r:supersu:s0"), new ArrayList<>()));
});
when(shell.open()).thenReturn(Single.just(shellSession).delay(2000, TimeUnit.MILLISECONDS));

Root.Builder builder = new Root.Builder();
assertThat(builder.shellBuilder(shellBuilder).suBinary(suBinary).build().blockingGet().getState(), is(Root.State.ROOTED));

builder = new Root.Builder().timeout(1000);
assertThat(builder.shellBuilder(shellBuilder).suBinary(suBinary).build().blockingGet().getState(), is(Root.State.UNAVAILABLE));

builder = new Root.Builder();
assertThat(builder.shellBuilder(shellBuilder).suBinary(suBinary).build().blockingGet().getState(), is(Root.State.ROOTED));
}
Expand Down

0 comments on commit f983a5f

Please sign in to comment.