diff --git a/jni/redsocks/redsocks.c b/jni/redsocks/redsocks.c index 878576ff..d5512497 100644 --- a/jni/redsocks/redsocks.c +++ b/jni/redsocks/redsocks.c @@ -780,7 +780,7 @@ static int redsocks_init_instance(redsocks_instance *instance) int on = 1; int fd = -1; - fd = socket(AF_INET, SOCK_STREAM, 0); + fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (fd == -1) { log_errno(LOG_ERR, "socket"); goto fail; diff --git a/src/main/java/eu/chainfire/libsuperuser/Application.java b/src/main/java/eu/chainfire/libsuperuser/Application.java index 55bbf320..a439517f 100644 --- a/src/main/java/eu/chainfire/libsuperuser/Application.java +++ b/src/main/java/eu/chainfire/libsuperuser/Application.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/eu/chainfire/libsuperuser/Debug.java b/src/main/java/eu/chainfire/libsuperuser/Debug.java index 4fda587a..5b1e4381 100644 --- a/src/main/java/eu/chainfire/libsuperuser/Debug.java +++ b/src/main/java/eu/chainfire/libsuperuser/Debug.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package eu.chainfire.libsuperuser; import android.os.Looper; import android.util.Log; + import com.github.shadowsocks.BuildConfig; /** diff --git a/src/main/java/eu/chainfire/libsuperuser/Shell.java b/src/main/java/eu/chainfire/libsuperuser/Shell.java index 496ba0f6..cf48036a 100644 --- a/src/main/java/eu/chainfire/libsuperuser/Shell.java +++ b/src/main/java/eu/chainfire/libsuperuser/Shell.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,20 +38,23 @@ import android.os.Looper; import eu.chainfire.libsuperuser.StreamGobbler.OnLineListener; /** - * Class providing functionality to execute commands in a (root) shell + * Class providing functionality to execute commands in a (root) shell */ -public class Shell { +public class Shell { /** - *

Runs commands using the supplied shell, and returns the output, or null in - * case of errors.

- * - *

This method is deprecated and only provided for backwards compatibility. - * Use {@link #run(String, String[], String[], boolean)} instead, and see that - * same method for usage notes.

+ *

+ * Runs commands using the supplied shell, and returns the output, or null + * in case of errors. + *

+ *

+ * This method is deprecated and only provided for backwards compatibility. + * Use {@link #run(String, String[], String[], boolean)} instead, and see + * that same method for usage notes. + *

* * @param shell The shell to use for executing the commands * @param commands The commands to execute - * @param wantSTDERR Return STDERR in the output ? + * @param wantSTDERR Return STDERR in the output ? * @return Output of the commands, or null in case of an error */ @Deprecated @@ -60,38 +63,48 @@ public class Shell { } /** - *

Runs commands using the supplied shell, and returns the output, or null in - * case of errors.

- * - *

Note that due to compatibility with older Android versions, - * wantSTDERR is not implemented using redirectErrorStream, but rather appended - * to the output. STDOUT and STDERR are thus not guaranteed to be in the correct - * order in the output.

- * - *

Note as well that this code will intentionally crash when run in debug mode - * from the main thread of the application. You should always execute shell - * commands from a background thread.

- * - *

When in debug mode, the code will also excessively log the commands passed to - * and the output returned from the shell.

- * - *

Though this function uses background threads to gobble STDOUT and STDERR so - * a deadlock does not occur if the shell produces massive output, the output is - * still stored in a List<String>, and as such doing something like 'ls -lR /' - * will probably have you run out of memory.

+ *

+ * Runs commands using the supplied shell, and returns the output, or null + * in case of errors. + *

+ *

+ * Note that due to compatibility with older Android versions, wantSTDERR is + * not implemented using redirectErrorStream, but rather appended to the + * output. STDOUT and STDERR are thus not guaranteed to be in the correct + * order in the output. + *

+ *

+ * Note as well that this code will intentionally crash when run in debug + * mode from the main thread of the application. You should always execute + * shell commands from a background thread. + *

+ *

+ * When in debug mode, the code will also excessively log the commands + * passed to and the output returned from the shell. + *

+ *

+ * Though this function uses background threads to gobble STDOUT and STDERR + * so a deadlock does not occur if the shell produces massive output, the + * output is still stored in a List<String>, and as such doing + * something like 'ls -lR /' will probably have you run out of + * memory. + *

* * @param shell The shell to use for executing the commands * @param commands The commands to execute - * @param environment List of all environment variables (in 'key=value' format) or null for defaults - * @param wantSTDERR Return STDERR in the output ? + * @param environment List of all environment variables (in 'key=value' + * format) or null for defaults + * @param wantSTDERR Return STDERR in the output ? * @return Output of the commands, or null in case of an error */ - public static List run(String shell, String[] commands, String[] environment, boolean wantSTDERR) { + public static List run(String shell, String[] commands, String[] environment, + boolean wantSTDERR) { String shellUpper = shell.toUpperCase(Locale.ENGLISH); if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { - // check if we're running in the main thread, and if so, crash if we're in debug mode, - // to let the developer know attention is needed here. + // check if we're running in the main thread, and if so, crash if + // we're in debug mode, to let the developer know attention is + // needed here. Debug.log(ShellOnMainThreadException.EXCEPTION_COMMAND); throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_COMMAND); @@ -119,11 +132,14 @@ public class Shell { } } - // setup our process, retrieve STDIN stream, and STDOUT/STDERR gobblers + // setup our process, retrieve STDIN stream, and STDOUT/STDERR + // gobblers Process process = Runtime.getRuntime().exec(shell, environment); DataOutputStream STDIN = new DataOutputStream(process.getOutputStream()); - StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(), res); - StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(), wantSTDERR ? res : null); + StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(), + res); + StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(), + wantSTDERR ? res : null); // start gobbling and write our commands to the shell STDOUT.start(); @@ -133,17 +149,25 @@ public class Shell { STDIN.write((write + "\n").getBytes("UTF-8")); STDIN.flush(); } - STDIN.write("exit\n".getBytes("UTF-8")); - STDIN.flush(); + try { + STDIN.write("exit\n".getBytes("UTF-8")); + STDIN.flush(); + } catch (IOException e) { + // happens if the script already contains the exit line - if + // there were a more serious issue, it would already have thrown + // an exception while writing the script to STDIN + } - // wait for our process to finish, while we gobble away in the background + // wait for our process to finish, while we gobble away in the + // background process.waitFor(); - // make sure our threads are done gobbling, our streams are closed, and the process is - // destroyed - while the latter two shouldn't be needed in theory, and may even produce - // warnings, in "normal" Java they are required for guaranteed cleanup of resources, so - // lets be safe and do this on Android as well - try { + // make sure our threads are done gobbling, our streams are closed, + // and the process is destroyed - while the latter two shouldn't be + // needed in theory, and may even produce warnings, in "normal" Java + // they are required for guaranteed cleanup of resources, so lets be + // safe and do this on Android as well + try { STDIN.close(); } catch (IOException e) { } @@ -154,7 +178,7 @@ public class Shell { // in case of su, 255 usually indicates access denied if (SU.isSU(shell) && (process.exitValue() == 255)) { res = null; - } + } } catch (IOException e) { // shell probably not found res = null; @@ -168,19 +192,21 @@ public class Shell { } protected static String[] availableTestCommands = new String[] { - "echo -BOC-", - "id" + "echo -BOC-", + "id" }; /** * See if the shell is alive, and if so, check the UID * * @param ret Standard output from running availableTestCommands - * @param checkForRoot true if we are expecting this shell to be running as root + * @param checkForRoot true if we are expecting this shell to be running as + * root * @return true on success, false on error */ protected static boolean parseAvailableResult(List ret, boolean checkForRoot) { - if (ret == null) return false; + if (ret == null) + return false; // this is only one of many ways this can be done boolean echo_seen = false; @@ -190,8 +216,10 @@ public class Shell { // id command is working, let's see if we are actually root return !checkForRoot || line.contains("uid=0"); } else if (line.contains("-BOC-")) { - // if we end up here, at least the su command starts some kind of shell, - // let's hope it has root privileges - no way to know without additional + // if we end up here, at least the su command starts some kind + // of shell, + // let's hope it has root privileges - no way to know without + // additional // native binaries echo_seen = true; } @@ -211,7 +239,9 @@ public class Shell { * @return Output of the command, or null in case of an error */ public static List run(String command) { - return Shell.run("sh", new String[] { command }, null, false); + return Shell.run("sh", new String[] { + command + }, null, false); } /** @@ -241,21 +271,30 @@ public class Shell { * if so which version. */ public static class SU { + private static Boolean isSELinuxEnforcing = null; + private static String[] suVersion = new String[] { + null, null + }; + /** * Runs command as root (if available) and return output * * @param command The command to run - * @return Output of the command, or null if root isn't available or in case of an error + * @return Output of the command, or null if root isn't available or in + * case of an error */ public static List run(String command) { - return Shell.run("su", new String[] { command }, null, false); + return Shell.run("su", new String[] { + command + }, null, false); } /** * Runs commands as root (if available) and return output * * @param commands The commands to run - * @return Output of the commands, or null if root isn't available or in case of an error + * @return Output of the commands, or null if root isn't available or in + * case of an error */ public static List run(List commands) { return Shell.run("su", commands.toArray(new String[commands.size()]), null, false); @@ -265,15 +304,17 @@ public class Shell { * Runs commands as root (if available) and return output * * @param commands The commands to run - * @return Output of the commands, or null if root isn't available or in case of an error + * @return Output of the commands, or null if root isn't available or in + * case of an error */ public static List run(String[] commands) { return Shell.run("su", commands, null, false); } /** - * Detects whether or not superuser access is available, by checking the output - * of the "id" command if available, checking if a shell runs at all otherwise + * Detects whether or not superuser access is available, by checking the + * output of the "id" command if available, checking if a shell runs at + * all otherwise * * @return True if superuser access available */ @@ -285,41 +326,65 @@ public class Shell { } /** - *

Detects the version of the su binary installed (if any), if supported by the binary. - * Most binaries support two different version numbers, the public version that is - * displayed to users, and an internal version number that is used for version number - * comparisons. Returns null if su not available or retrieving the version isn't supported.

+ *

+ * Detects the version of the su binary installed (if any), if supported + * by the binary. Most binaries support two different version numbers, + * the public version that is displayed to users, and an internal + * version number that is used for version number comparisons. Returns + * null if su not available or retrieving the version isn't supported. + *

+ *

+ * Note that su binary version and GUI (APK) version can be completely + * different. + *

+ *

+ * This function caches its result to improve performance on multiple + * calls + *

* - *

Note that su binary version and GUI (APK) version can be completely different.

- * - * @param internal Request human-readable version or application internal version + * @param internal Request human-readable version or application + * internal version * @return String containing the su version or null */ - public static String version(boolean internal) { - List ret = Shell.run( - internal ? "su -V" : "su -v", - new String[] { }, - null, - false - ); - if (ret == null) return null; + public static synchronized String version(boolean internal) { + int idx = internal ? 0 : 1; + if (suVersion[idx] == null) { + String version = null; - for (String line : ret) { - if (!internal) { - if (line.contains(".")) return line; - } else { - try { - if (Integer.parseInt(line) > 0) return line; - } catch(NumberFormatException e) { + List ret = Shell.run( + internal ? "su -V" : "su -v", + new String[] {}, + null, + false + ); + + if (ret != null) { + for (String line : ret) { + if (!internal) { + if (line.contains(".")) { + version = line; + break; + } + } else { + try { + if (Integer.parseInt(line) > 0) { + version = line; + break; + } + } catch (NumberFormatException e) { + } + } } } + + suVersion[idx] = version; } - return null; + return suVersion[idx]; } /** * Attempts to deduce if the shell command refers to a su shell - * + * * @param shell Shell command to run * @return Shell command appears to be su */ @@ -340,9 +405,9 @@ public class Shell { } /** - * Constructs a shell command to start a su shell using the supplied - * uid and SELinux context. This is can be an expensive operation, - * consider caching the result. + * Constructs a shell command to start a su shell using the supplied uid + * and SELinux context. This is can be an expensive operation, consider + * caching the result. * * @param uid Uid to use (0 == root) * @param context (SELinux) context name to use or null @@ -352,47 +417,16 @@ public class Shell { // su[ --context ][ ] String shell = "su"; - // First known firmware with SELinux built-in was a 4.2 (17) leak - if ((context != null) && (android.os.Build.VERSION.SDK_INT >= 17)) { - Boolean enforcing = null; + if ((context != null) && isSELinuxEnforcing()) { + String display = version(false); + String internal = version(true); - // Detect enforcing through sysfs, not always present - if (enforcing == null) { - File f = new File("/sys/fs/selinux/enforce"); - if (f.exists()) { - try { - InputStream is = new FileInputStream("/sys/fs/selinux/enforce"); - try { - enforcing = (is.read() == '1'); - } finally { - is.close(); - } - } catch (Exception e) { - } - } - } - - // 4.4+ builds are enforcing by default, take the gamble - if (enforcing == null) { - enforcing = (android.os.Build.VERSION.SDK_INT >= 19); - } - - // Switching to a context in permissive mode is not generally - // useful aside from audit testing, but we want to avoid - // switching the context due to the increased chance of issues. - if (enforcing) { - String display = version(false); - String internal = version(true); - - // We only know the format for SuperSU v1.90+ right now - if ( - (display != null) && - (internal != null) && - (display.endsWith("SUPERSU")) && - (Integer.valueOf(internal) >= 190) - ) { - shell = String.format(Locale.ENGLISH, "%s --context %s", shell, context); - } + // We only know the format for SuperSU v1.90+ right now + if ((display != null) && + (internal != null) && + (display.endsWith("SUPERSU")) && + (Integer.valueOf(internal) >= 190)) { + shell = String.format(Locale.ENGLISH, "%s --context %s", shell, context); } } @@ -404,29 +438,84 @@ public class Shell { return shell; } + + /** + * Constructs a shell command to start a su shell connected to mount + * master daemon, to perform public mounts on Android 4.3+ (or 4.2+ in + * SELinux enforcing mode) + * + * @return Shell command + */ + public static String shellMountMaster() { + if (android.os.Build.VERSION.SDK_INT >= 17) { + return "su --mount-master"; + } + return "su"; + } + + /** + * Detect if SELinux is set to enforcing, caches result + * + * @return true if SELinux set to enforcing, or false in the case of + * permissive or not present + */ + public static synchronized boolean isSELinuxEnforcing() { + if (isSELinuxEnforcing == null) { + Boolean enforcing = null; + + // First known firmware with SELinux built-in was a 4.2 (17) + // leak + if (android.os.Build.VERSION.SDK_INT >= 17) { + // Detect enforcing through sysfs, not always present + if (enforcing == null) { + File f = new File("/sys/fs/selinux/enforce"); + if (f.exists()) { + try { + InputStream is = new FileInputStream("/sys/fs/selinux/enforce"); + try { + enforcing = (is.read() == '1'); + } finally { + is.close(); + } + } catch (Exception e) { + } + } + } + + // 4.4+ builds are enforcing by default, take the gamble + if (enforcing == null) { + enforcing = (android.os.Build.VERSION.SDK_INT >= 19); + } + } + + if (enforcing == null) { + enforcing = false; + } + + isSELinuxEnforcing = enforcing; + } + return isSELinuxEnforcing; + } + + /** + *

+ * Clears results cached by isSELinuxEnforcing() and version(boolean + * internal) calls. + *

+ *

+ * Most apps should never need to call this, as neither enforcing status + * nor su version is likely to change on a running device - though it is + * not impossible. + *

+ */ + public static synchronized void clearCachedResults() { + isSELinuxEnforcing = null; + suVersion[0] = null; + suVersion[1] = null; + } } - /** - * Command result callback, notifies the recipient of the completion of a command - * block, including the (last) exit code, and the full output - */ - public interface OnCommandResultListener { - /** - *

Command result callback

- * - *

Depending on how and on which thread the shell was created, this callback - * may be executed on one of the gobbler threads. In that case, it is important - * the callback returns as quickly as possible, as delays in this callback may - * pause the native process or even result in a deadlock

- * - *

See {@link Shell.Interactive} for threading details

- * - * @param commandCode Value previously supplied to addCommand - * @param exitCode Exit code of the last command in the block - * @param output All output generated by the command block - */ - public void onCommandResult(int commandCode, int exitCode, List output); - + private interface OnResult { // for any onCommandResult callback public static final int WATCHDOG_EXIT = -1; public static final int SHELL_DIED = -2; @@ -437,6 +526,60 @@ public class Shell { public static final int SHELL_RUNNING = 0; } + /** + * Command result callback, notifies the recipient of the completion of a + * command block, including the (last) exit code, and the full output + */ + public interface OnCommandResultListener extends OnResult { + /** + *

+ * Command result callback + *

+ *

+ * Depending on how and on which thread the shell was created, this + * callback may be executed on one of the gobbler threads. In that case, + * it is important the callback returns as quickly as possible, as + * delays in this callback may pause the native process or even result + * in a deadlock + *

+ *

+ * See {@link Shell.Interactive} for threading details + *

+ * + * @param commandCode Value previously supplied to addCommand + * @param exitCode Exit code of the last command in the block + * @param output All output generated by the command block + */ + public void onCommandResult(int commandCode, int exitCode, List output); + } + + /** + * Command per line callback for parsing the output line by line without + * buffering It also notifies the recipient of the completion of a command + * block, including the (last) exit code. + */ + public interface OnCommandLineListener extends OnResult, OnLineListener { + /** + *

+ * Command result callback + *

+ *

+ * Depending on how and on which thread the shell was created, this + * callback may be executed on one of the gobbler threads. In that case, + * it is important the callback returns as quickly as possible, as + * delays in this callback may pause the native process or even result + * in a deadlock + *

+ *

+ * See {@link Shell.Interactive} for threading details + *

+ * + * @param commandCode Value previously supplied to addCommand + * @param exitCode Exit code of the last command in the block + */ + public void onCommandResult(int commandCode, int exitCode); + } + /** * Internal class to store command block properties */ @@ -446,12 +589,16 @@ public class Shell { private final String[] commands; private final int code; private final OnCommandResultListener onCommandResultListener; + private final OnCommandLineListener onCommandLineListener; private final String marker; - public Command(String[] commands, int code, OnCommandResultListener onCommandResultListener) { + public Command(String[] commands, int code, + OnCommandResultListener onCommandResultListener, + OnCommandLineListener onCommandLineListener) { this.commands = commands; this.code = code; this.onCommandResultListener = onCommandResultListener; + this.onCommandLineListener = onCommandLineListener; this.marker = UUID.randomUUID().toString() + String.format("-%08x", ++commandCounter); } } @@ -471,24 +618,38 @@ public class Shell { private int watchdogTimeout = 0; /** - *

Set a custom handler that will be used to post all callbacks to

- * - *

See {@link Shell.Interactive} for further details on threading and handlers

+ *

+ * Set a custom handler that will be used to post all callbacks to + *

+ *

+ * See {@link Shell.Interactive} for further details on threading and + * handlers + *

* * @param handler Handler to use * @return This Builder object for method chaining */ - public Builder setHandler(Handler handler) { this.handler = handler; return this; } + public Builder setHandler(Handler handler) { + this.handler = handler; + return this; + } /** - *

Automatically create a handler if possible ? Default to true

- * - *

See {@link Shell.Interactive} for further details on threading and handlers

+ *

+ * Automatically create a handler if possible ? Default to true + *

+ *

+ * See {@link Shell.Interactive} for further details on threading and + * handlers + *

* * @param autoHandler Auto-create handler ? * @return This Builder object for method chaining */ - public Builder setAutoHandler(boolean autoHandler) { this.autoHandler = autoHandler; return this; } + public Builder setAutoHandler(boolean autoHandler) { + this.autoHandler = autoHandler; + return this; + } /** * Set shell binary to use. Usually "sh" or "su", do not use a full path @@ -497,21 +658,28 @@ public class Shell { * @param shell Shell to use * @return This Builder object for method chaining */ - public Builder setShell(String shell) { this.shell = shell; return this; } + public Builder setShell(String shell) { + this.shell = shell; + return this; + } /** * Convenience function to set "sh" as used shell * * @return This Builder object for method chaining */ - public Builder useSH() { return setShell("sh"); } + public Builder useSH() { + return setShell("sh"); + } /** * Convenience function to set "su" as used shell * * @return This Builder object for method chaining */ - public Builder useSU() { return setShell("su"); } + public Builder useSU() { + return setShell("su"); + } /** * Set if error output should be appended to command block result output @@ -519,7 +687,10 @@ public class Shell { * @param wantSTDERR Want error output ? * @return This Builder object for method chaining */ - public Builder setWantSTDERR(boolean wantSTDERR) { this.wantSTDERR = wantSTDERR; return this; } + public Builder setWantSTDERR(boolean wantSTDERR) { + this.wantSTDERR = wantSTDERR; + return this; + } /** * Add or update an environment variable @@ -528,7 +699,10 @@ public class Shell { * @param value Value of the environment variable * @return This Builder object for method chaining */ - public Builder addEnvironment(String key, String value) { environment.put(key, value); return this; } + public Builder addEnvironment(String key, String value) { + environment.put(key, value); + return this; + } /** * Add or update environment variables @@ -536,7 +710,10 @@ public class Shell { * @param addEnvironment Map of environment variables * @return This Builder object for method chaining */ - public Builder addEnvironment(Map addEnvironment) { environment.putAll(addEnvironment); return this; } + public Builder addEnvironment(Map addEnvironment) { + environment.putAll(addEnvironment); + return this; + } /** * Add a command to execute @@ -544,19 +721,30 @@ public class Shell { * @param command Command to execute * @return This Builder object for method chaining */ - public Builder addCommand(String command) { return addCommand(command, 0, null); } + public Builder addCommand(String command) { + return addCommand(command, 0, null); + } /** - *

Add a command to execute, with a callback to be called on completion

- * - *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ *

+ * Add a command to execute, with a callback to be called on completion + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

* * @param command Command to execute * @param code User-defined value passed back to the callback * @param onCommandResultListener Callback to be called on completion * @return This Builder object for method chaining */ - public Builder addCommand(String command, int code, OnCommandResultListener onCommandResultListener) { return addCommand(new String[] { command }, code, onCommandResultListener); } + public Builder addCommand(String command, int code, + OnCommandResultListener onCommandResultListener) { + return addCommand(new String[] { + command + }, code, onCommandResultListener); + } /** * Add commands to execute @@ -564,19 +752,31 @@ public class Shell { * @param commands Commands to execute * @return This Builder object for method chaining */ - public Builder addCommand(List commands) { return addCommand(commands, 0, null); } + public Builder addCommand(List commands) { + return addCommand(commands, 0, null); + } /** - *

Add commands to execute, with a callback to be called on completion (of all commands)

- * - *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ *

+ * Add commands to execute, with a callback to be called on completion + * (of all commands) + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

* * @param commands Commands to execute * @param code User-defined value passed back to the callback - * @param onCommandResultListener Callback to be called on completion (of all commands) + * @param onCommandResultListener Callback to be called on completion + * (of all commands) * @return This Builder object for method chaining */ - public Builder addCommand(List commands, int code, OnCommandResultListener onCommandResultListener) { return addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); } + public Builder addCommand(List commands, int code, + OnCommandResultListener onCommandResultListener) { + return addCommand(commands.toArray(new String[commands.size()]), code, + onCommandResultListener); + } /** * Add commands to execute @@ -584,58 +784,96 @@ public class Shell { * @param commands Commands to execute * @return This Builder object for method chaining */ - public Builder addCommand(String[] commands) { return addCommand(commands, 0, null); } + public Builder addCommand(String[] commands) { + return addCommand(commands, 0, null); + } /** - *

Add commands to execute, with a callback to be called on completion (of all commands)

- * - *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ *

+ * Add commands to execute, with a callback to be called on completion + * (of all commands) + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

* * @param commands Commands to execute * @param code User-defined value passed back to the callback - * @param onCommandResultListener Callback to be called on completion (of all commands) + * @param onCommandResultListener Callback to be called on completion + * (of all commands) * @return This Builder object for method chaining */ - public Builder addCommand(String[] commands, int code, OnCommandResultListener onCommandResultListener) { this.commands.add(new Command(commands, code, onCommandResultListener)); return this; } + public Builder addCommand(String[] commands, int code, + OnCommandResultListener onCommandResultListener) { + this.commands.add(new Command(commands, code, onCommandResultListener, null)); + return this; + } /** - *

Set a callback called for every line output to STDOUT by the shell

- * - *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ *

+ * Set a callback called for every line output to STDOUT by the shell + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

* * @param onLineListener Callback to be called for each line * @return This Builder object for method chaining */ - public Builder setOnSTDOUTLineListener(OnLineListener onLineListener) { this.onSTDOUTLineListener = onLineListener; return this; } + public Builder setOnSTDOUTLineListener(OnLineListener onLineListener) { + this.onSTDOUTLineListener = onLineListener; + return this; + } /** - *

Set a callback called for every line output to STDERR by the shell

- * - *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ *

+ * Set a callback called for every line output to STDERR by the shell + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

* * @param onLineListener Callback to be called for each line * @return This Builder object for method chaining */ - public Builder setOnSTDERRLineListener(OnLineListener onLineListener) { this.onSTDERRLineListener = onLineListener; return this; } + public Builder setOnSTDERRLineListener(OnLineListener onLineListener) { + this.onSTDERRLineListener = onLineListener; + return this; + } /** - *

Enable command timeout callback

- * - *

This will invoke the onCommandResult() callback with exitCode WATCHDOG_EXIT if a command takes longer than watchdogTimeout - * seconds to complete.

- * - *

If a watchdog timeout occurs, it generally means that the Interactive session is out of sync with the shell process. The - * caller should close the current session and open a new one.

+ *

+ * Enable command timeout callback + *

+ *

+ * This will invoke the onCommandResult() callback with exitCode + * WATCHDOG_EXIT if a command takes longer than watchdogTimeout seconds + * to complete. + *

+ *

+ * If a watchdog timeout occurs, it generally means that the Interactive + * session is out of sync with the shell process. The caller should + * close the current session and open a new one. + *

* * @param watchdogTimeout Timeout, in seconds; 0 to disable * @return This Builder object for method chaining */ - public Builder setWatchdogTimeout(int watchdogTimeout) { this.watchdogTimeout = watchdogTimeout; return this; } + public Builder setWatchdogTimeout(int watchdogTimeout) { + this.watchdogTimeout = watchdogTimeout; + return this; + } /** - *

Enable/disable reduced logcat output

- * - *

Note that this is a global setting

+ *

+ * Enable/disable reduced logcat output + *

+ *

+ * Note that this is a global setting + *

* * @param useMinimal true for reduced output, false for full output * @return This Builder object for method chaining @@ -648,11 +886,13 @@ public class Shell { /** * Construct a {@link Shell.Interactive} instance, and start the shell */ - public Interactive open() { return new Interactive(this, null); } + public Interactive open() { + return new Interactive(this, null); + } /** - * Construct a {@link Shell.Interactive} instance, try to start the shell, and - * call onCommandResultListener to report success or failure + * Construct a {@link Shell.Interactive} instance, try to start the + * shell, and call onCommandResultListener to report success or failure * * @param onCommandResultListener Callback to return shell open status */ @@ -662,55 +902,70 @@ public class Shell { } /** - *

An interactive shell - initially created with {@link Shell.Builder} - that - * executes blocks of commands you supply in the background, optionally calling - * callbacks as each block completes.

- * - *

STDERR output can be supplied as well, but due to compatibility with older - * Android versions, wantSTDERR is not implemented using redirectErrorStream, - * but rather appended to the output. STDOUT and STDERR are thus not guaranteed to - * be in the correct order in the output.

- * - *

Note as well that the close() and waitForIdle() methods will intentionally - * crash when run in debug mode from the main thread of the application. Any blocking - * call should be run from a background thread.

- * - *

When in debug mode, the code will also excessively log the commands passed to - * and the output returned from the shell.

- * - *

Though this function uses background threads to gobble STDOUT and STDERR so - * a deadlock does not occur if the shell produces massive output, the output is - * still stored in a List<String>, and as such doing something like 'ls -lR /' - * will probably have you run out of memory when using a - * {@link Shell.OnCommandResultListener}. A work-around is to not supply this callback, - * but using (only) {@link Shell.Builder#setOnSTDOUTLineListener(OnLineListener)}. This - * way, an internal buffer will not be created and wasting your memory.

- * + *

+ * An interactive shell - initially created with {@link Shell.Builder} - + * that executes blocks of commands you supply in the background, optionally + * calling callbacks as each block completes. + *

+ *

+ * STDERR output can be supplied as well, but due to compatibility with + * older Android versions, wantSTDERR is not implemented using + * redirectErrorStream, but rather appended to the output. STDOUT and STDERR + * are thus not guaranteed to be in the correct order in the output. + *

+ *

+ * Note as well that the close() and waitForIdle() methods will + * intentionally crash when run in debug mode from the main thread of the + * application. Any blocking call should be run from a background thread. + *

+ *

+ * When in debug mode, the code will also excessively log the commands + * passed to and the output returned from the shell. + *

+ *

+ * Though this function uses background threads to gobble STDOUT and STDERR + * so a deadlock does not occur if the shell produces massive output, the + * output is still stored in a List<String>, and as such doing + * something like 'ls -lR /' will probably have you run out of + * memory when using a {@link Shell.OnCommandResultListener}. A work-around + * is to not supply this callback, but using (only) + * {@link Shell.Builder#setOnSTDOUTLineListener(OnLineListener)}. This way, + * an internal buffer will not be created and wasting your memory. + *

*

Callbacks, threads and handlers

- * - *

On which thread the callbacks execute is dependent on your initialization. You can - * supply a custom Handler using {@link Shell.Builder#setHandler(Handler)} if needed. - * If you do not supply a custom Handler - unless you set {@link Shell.Builder#setAutoHandler(boolean)} - * to false - a Handler will be auto-created if the thread used for instantiation - * of the object has a Looper.

- * - *

If no Handler was supplied and it was also not auto-created, all callbacks will - * be called from either the STDOUT or STDERR gobbler threads. These are important - * threads that should be blocked as little as possible, as blocking them may in rare - * cases pause the native process or even create a deadlock.

- * - *

The main thread must certainly have a Looper, thus if you call {@link Shell.Builder#open()} - * from the main thread, a handler will (by default) be auto-created, and all the callbacks - * will be called on the main thread. While this is often convenient and easy to code with, - * you should be aware that if your callbacks are 'expensive' to execute, this may negatively - * impact UI performance.

- * - *

Background threads usually do not have a Looper, so calling {@link Shell.Builder#open()} - * from such a background thread will (by default) result in all the callbacks being executed - * in one of the gobbler threads. You will have to make sure the code you execute in these callbacks - * is thread-safe.

+ *

+ * On which thread the callbacks execute is dependent on your + * initialization. You can supply a custom Handler using + * {@link Shell.Builder#setHandler(Handler)} if needed. If you do not supply + * a custom Handler - unless you set + * {@link Shell.Builder#setAutoHandler(boolean)} to false - a Handler will + * be auto-created if the thread used for instantiation of the object has a + * Looper. + *

+ *

+ * If no Handler was supplied and it was also not auto-created, all + * callbacks will be called from either the STDOUT or STDERR gobbler + * threads. These are important threads that should be blocked as little as + * possible, as blocking them may in rare cases pause the native process or + * even create a deadlock. + *

+ *

+ * The main thread must certainly have a Looper, thus if you call + * {@link Shell.Builder#open()} from the main thread, a handler will (by + * default) be auto-created, and all the callbacks will be called on the + * main thread. While this is often convenient and easy to code with, you + * should be aware that if your callbacks are 'expensive' to execute, this + * may negatively impact UI performance. + *

+ *

+ * Background threads usually do not have a Looper, so calling + * {@link Shell.Builder#open()} from such a background thread will (by + * default) result in all the callbacks being executed in one of the gobbler + * threads. You will have to make sure the code you execute in these + * callbacks is thread-safe. + *

*/ - public static class Interactive { + public static class Interactive { private final Handler handler; private final boolean autoHandler; private final String shell; @@ -724,7 +979,7 @@ public class Shell { private Process process = null; private DataOutputStream STDIN = null; private StreamGobbler STDOUT = null; - private StreamGobbler STDERR = null; + private StreamGobbler STDERR = null; private ScheduledThreadPoolExecutor watchdog = null; private volatile boolean running = false; @@ -747,7 +1002,8 @@ public class Shell { * * @param builder Builder class to take values from */ - private Interactive(final Builder builder, final OnCommandResultListener onCommandResultListener) { + private Interactive(final Builder builder, + final OnCommandResultListener onCommandResultListener) { autoHandler = builder.autoHandler; shell = builder.shell; wantSTDERR = builder.wantSTDERR; @@ -757,7 +1013,8 @@ public class Shell { onSTDERRLineListener = builder.onSTDERRLineListener; watchdogTimeout = builder.watchdogTimeout; - // If a looper is available, we offload the callbacks from the gobbling threads + // If a looper is available, we offload the callbacks from the + // gobbling threads // to whichever thread created us. Would normally do this in open(), // but then we could not declare handler as final if ((Looper.myLooper() != null) && (builder.handler == null) && autoHandler) { @@ -770,11 +1027,13 @@ public class Shell { if (onCommandResultListener == null) { return; } else if (ret == false) { - onCommandResultListener.onCommandResult(0, OnCommandResultListener.SHELL_EXEC_FAILED, null); + onCommandResultListener.onCommandResult(0, + OnCommandResultListener.SHELL_EXEC_FAILED, null); return; } - // Allow up to 60 seconds for SuperSU/Superuser dialog, then enable the user-specified + // Allow up to 60 seconds for SuperSU/Superuser dialog, then enable + // the user-specified // timeout for all subsequent operations watchdogTimeout = 60; addCommand(Shell.availableTestCommands, 0, new OnCommandResultListener() { @@ -791,11 +1050,11 @@ public class Shell { } @Override - protected void finalize() throws Throwable { + protected void finalize() throws Throwable { if (!closed && Debug.getSanityChecksEnabledEffective()) { // waste of resources Debug.log(ShellNotClosedException.EXCEPTION_NOT_CLOSED); - throw new ShellNotClosedException(); + throw new ShellNotClosedException(); } super.finalize(); } @@ -805,73 +1064,170 @@ public class Shell { * * @param command Command to execute */ - public void addCommand(String command) { addCommand(command, 0, null); } + public void addCommand(String command) { + addCommand(command, 0, (OnCommandResultListener) null); + } /** - *

Add a command to execute, with a callback to be called on completion

- * - *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ *

+ * Add a command to execute, with a callback to be called on completion + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

* * @param command Command to execute * @param code User-defined value passed back to the callback * @param onCommandResultListener Callback to be called on completion */ - public void addCommand(String command, int code, OnCommandResultListener onCommandResultListener) { addCommand(new String[] { command }, code, onCommandResultListener); } + public void addCommand(String command, int code, + OnCommandResultListener onCommandResultListener) { + addCommand(new String[] { + command + }, code, onCommandResultListener); + } + + /** + *

+ * Add a command to execute, with a callback. This callback gobbles the + * output line by line without buffering it and also returns the result + * code on completion. + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

+ * + * @param command Command to execute + * @param code User-defined value passed back to the callback + * @param onCommandLineListener Callback + */ + public void addCommand(String command, int code, OnCommandLineListener onCommandLineListener) { + addCommand(new String[] { + command + }, code, onCommandLineListener); + } /** * Add commands to execute * * @param commands Commands to execute */ - public void addCommand(List commands) { addCommand(commands, 0, null); } + public void addCommand(List commands) { + addCommand(commands, 0, (OnCommandResultListener) null); + } /** - *

Add commands to execute, with a callback to be called on completion (of all commands)

- * - *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ *

+ * Add commands to execute, with a callback to be called on completion + * (of all commands) + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

* * @param commands Commands to execute * @param code User-defined value passed back to the callback - * @param onCommandResultListener Callback to be called on completion (of all commands) + * @param onCommandResultListener Callback to be called on completion + * (of all commands) */ - public void addCommand(List commands, int code, OnCommandResultListener onCommandResultListener) { addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); } + public void addCommand(List commands, int code, + OnCommandResultListener onCommandResultListener) { + addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); + } + + /** + *

+ * Add commands to execute, with a callback. This callback gobbles the + * output line by line without buffering it and also returns the result + * code on completion. + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandLineListener Callback + */ + public void addCommand(List commands, int code, + OnCommandLineListener onCommandLineListener) { + addCommand(commands.toArray(new String[commands.size()]), code, onCommandLineListener); + } /** * Add commands to execute * * @param commands Commands to execute */ - public void addCommand(String[] commands) { addCommand(commands, 0, null); } + public void addCommand(String[] commands) { + addCommand(commands, 0, (OnCommandResultListener) null); + } /** - *

Add commands to execute, with a callback to be called on completion (of all commands)

- * - *

The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details

+ *

+ * Add commands to execute, with a callback to be called on completion + * (of all commands) + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

* * @param commands Commands to execute * @param code User-defined value passed back to the callback - * @param onCommandResultListener Callback to be called on completion (of all commands) + * @param onCommandResultListener Callback to be called on completion + * (of all commands) */ - public synchronized void addCommand(String[] commands, int code, OnCommandResultListener onCommandResultListener) { - this.commands.add(new Command(commands, code, onCommandResultListener)); + public synchronized void addCommand(String[] commands, int code, + OnCommandResultListener onCommandResultListener) { + this.commands.add(new Command(commands, code, onCommandResultListener, null)); runNextCommand(); } /** - * Run the next command if any and if ready, signals idle state if no commands left + *

+ * Add commands to execute, with a callback. This callback gobbles the + * output line by line without buffering it and also returns the result + * code on completion. + *

+ *

+ * The thread on which the callback executes is dependent on various + * factors, see {@link Shell.Interactive} for further details + *

+ * + * @param commands Commands to execute + * @param code User-defined value passed back to the callback + * @param onCommandLineListener Callback */ - private void runNextCommand() { - runNextCommand(true); + public synchronized void addCommand(String[] commands, int code, + OnCommandLineListener onCommandLineListener) { + this.commands.add(new Command(commands, code, null, onCommandLineListener)); + runNextCommand(); } /** - * Called from a ScheduledThreadPoolExecutor timer thread every second when there is an outstanding command + * Run the next command if any and if ready, signals idle state if no + * commands left + */ + private void runNextCommand() { + runNextCommand(true); + } + + /** + * Called from a ScheduledThreadPoolExecutor timer thread every second + * when there is an outstanding command */ private synchronized void handleWatchdog() { final int exitCode; - if (watchdog == null) return; - if (watchdogTimeout == 0) return; + if (watchdog == null) + return; + if (watchdogTimeout == 0) + return; if (!isRunning()) { exitCode = OnCommandResultListener.SHELL_DIED; @@ -927,13 +1283,14 @@ public class Shell { /** * Run the next command if any and if ready * - * @param notifyIdle signals idle state if no commands left ? + * @param notifyIdle signals idle state if no commands left ? */ private void runNextCommand(boolean notifyIdle) { // must always be called from a synchronized method boolean running = isRunning(); - if (!running) idle = true; + if (!running) + idle = true; if (running && idle && (commands.size() > 0)) { Command command = commands.get(0); @@ -942,22 +1299,25 @@ public class Shell { buffer = null; lastExitCode = 0; lastMarkerSTDOUT = null; - lastMarkerSTDERR = null; + lastMarkerSTDERR = null; if (command.commands.length > 0) { try { if (command.onCommandResultListener != null) { - // no reason to store the output if we don't have an OnCommandResultListener - // user should catch the output with an OnLineListener in this case - buffer = Collections.synchronizedList(new ArrayList()); + // no reason to store the output if we don't have an + // OnCommandResultListener + // user should catch the output with an + // OnLineListener in this case + buffer = Collections.synchronizedList(new ArrayList()); } idle = false; this.command = command; startWatchdog(); for (String write : command.commands) { - Debug.logCommand(String.format("[%s+] %s", shell.toUpperCase(Locale.ENGLISH), write)); - STDIN.write((write + "\n").getBytes("UTF-8")); + Debug.logCommand(String.format("[%s+] %s", + shell.toUpperCase(Locale.ENGLISH), write)); + STDIN.write((write + "\n").getBytes("UTF-8")); } STDIN.write(("echo " + command.marker + " $?\n").getBytes("UTF-8")); STDIN.write(("echo " + command.marker + " >&2\n").getBytes("UTF-8")); @@ -966,7 +1326,7 @@ public class Shell { } } else { runNextCommand(false); - } + } } else if (!running) { // our shell died for unknown reasons - abort all submissions while (commands.size() > 0) { @@ -975,7 +1335,7 @@ public class Shell { } if (idle && notifyIdle) { - synchronized(idleSync) { + synchronized (idleSync) { idleSync.notifyAll(); } } @@ -985,17 +1345,15 @@ public class Shell { * Processes a STDOUT/STDERR line containing an end/exitCode marker */ private synchronized void processMarker() { - if (command.marker.equals(lastMarkerSTDOUT) && (command.marker.equals(lastMarkerSTDERR))) { - if (buffer != null) { - postCallback(command, lastExitCode, buffer); - } - + if (command.marker.equals(lastMarkerSTDOUT) + && (command.marker.equals(lastMarkerSTDERR))) { + postCallback(command, lastExitCode, buffer); stopWatchdog(); command = null; buffer = null; idle = true; - runNextCommand(); - } + runNextCommand(); + } } /** @@ -1011,7 +1369,7 @@ public class Shell { final OnLineListener fListener = listener; startCallback(); - handler.post(new Runnable() { + handler.post(new Runnable() { @Override public void run() { try { @@ -1024,7 +1382,7 @@ public class Shell { } else { listener.onLine(line); } - } + } } /** @@ -1033,7 +1391,7 @@ public class Shell { * @param line Line to add */ private synchronized void addBuffer(String line) { - if (buffer != null) { + if (buffer != null) { buffer.add(line); } } @@ -1050,12 +1408,17 @@ public class Shell { /** * Schedule a callback to run on the appropriate thread */ - private void postCallback(final Command fCommand, final int fExitCode, final List fOutput) { - if (fCommand.onCommandResultListener == null) { + private void postCallback(final Command fCommand, final int fExitCode, + final List fOutput) { + if (fCommand.onCommandResultListener == null && fCommand.onCommandLineListener == null) { return; } if (handler == null) { - fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, fOutput); + if ((fCommand.onCommandResultListener != null) && (fOutput != null)) + fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, + fOutput); + if (fCommand.onCommandLineListener != null) + fCommand.onCommandLineListener.onCommandResult(fCommand.code, fExitCode); return; } startCallback(); @@ -1063,7 +1426,12 @@ public class Shell { @Override public void run() { try { - fCommand.onCommandResultListener.onCommandResult(fCommand.code, fExitCode, fOutput); + if ((fCommand.onCommandResultListener != null) && (fOutput != null)) + fCommand.onCommandResultListener.onCommandResult(fCommand.code, + fExitCode, fOutput); + if (fCommand.onCommandLineListener != null) + fCommand.onCommandLineListener + .onCommandResult(fCommand.code, fExitCode); } finally { endCallback(); } @@ -1072,7 +1440,8 @@ public class Shell { } /** - * Decrease callback counter, signals callback complete state when dropped to 0 + * Decrease callback counter, signals callback complete state when + * dropped to 0 */ private void endCallback() { synchronized (callbackSync) { @@ -1084,8 +1453,8 @@ public class Shell { } /** - * Internal call that launches the shell, starts gobbling, and starts executing commands. - * See {@link Shell.Interactive} + * Internal call that launches the shell, starts gobbling, and starts + * executing commands. See {@link Shell.Interactive} * * @return Opened successfully ? */ @@ -1093,7 +1462,8 @@ public class Shell { Debug.log(String.format("[%s%%] START", shell.toUpperCase(Locale.ENGLISH))); try { - // setup our process, retrieve STDIN stream, and STDOUT/STDERR gobblers + // setup our process, retrieve STDIN stream, and STDOUT/STDERR + // gobblers if (environment.size() == 0) { process = Runtime.getRuntime().exec(shell); } else { @@ -1104,50 +1474,55 @@ public class Shell { String[] env = new String[newEnvironment.size()]; for (Map.Entry entry : newEnvironment.entrySet()) { env[i] = entry.getKey() + "=" + entry.getValue(); - i++; + i++; } process = Runtime.getRuntime().exec(shell, env); } STDIN = new DataOutputStream(process.getOutputStream()); - STDOUT = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "-", process.getInputStream(), new OnLineListener() { - @Override - public void onLine(String line) { - synchronized (Interactive.this) { - if (command == null) { - return; - } - if (line.startsWith(command.marker)) { - try { - lastExitCode = Integer.valueOf(line.substring(command.marker.length() + 1), 10); - } catch (Exception e) { + STDOUT = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "-", + process.getInputStream(), new OnLineListener() { + @Override + public void onLine(String line) { + synchronized (Interactive.this) { + if (command == null) { + return; + } + if (line.startsWith(command.marker)) { + try { + lastExitCode = Integer.valueOf( + line.substring(command.marker.length() + 1), 10); + } catch (Exception e) { + } + lastMarkerSTDOUT = command.marker; + processMarker(); + } else { + addBuffer(line); + processLine(line, onSTDOUTLineListener); + processLine(line, command.onCommandLineListener); + } } - lastMarkerSTDOUT = command.marker; - processMarker(); - } else { - addBuffer(line); - processLine(line, onSTDOUTLineListener); } - } - } - }); - STDERR = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "*", process.getErrorStream(), new OnLineListener() { - @Override - public void onLine(String line) { - synchronized (Interactive.this) { - if (command == null) { - return; + }); + STDERR = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "*", + process.getErrorStream(), new OnLineListener() { + @Override + public void onLine(String line) { + synchronized (Interactive.this) { + if (command == null) { + return; + } + if (line.startsWith(command.marker)) { + lastMarkerSTDERR = command.marker; + processMarker(); + } else { + if (wantSTDERR) + addBuffer(line); + processLine(line, onSTDERRLineListener); + } + } } - if (line.startsWith(command.marker)) { - lastMarkerSTDERR = command.marker; - processMarker(); - } else { - if (wantSTDERR) addBuffer(line); - processLine(line, onSTDERRLineListener); - } - } - } - }); + }); // start gobbling and write our commands to the shell STDOUT.start(); @@ -1166,43 +1541,49 @@ public class Shell { } /** - * Close shell and clean up all resources. Call this when you are done with the shell. - * If the shell is not idle (all commands completed) you should not call this method - * from the main UI thread because it may block for a long time. This method will - * intentionally crash your app (if in debug mode) if you try to do this anyway. + * Close shell and clean up all resources. Call this when you are done + * with the shell. If the shell is not idle (all commands completed) you + * should not call this method from the main UI thread because it may + * block for a long time. This method will intentionally crash your app + * (if in debug mode) if you try to do this anyway. */ public void close() { boolean _idle = isIdle(); // idle must be checked synchronized synchronized (this) { - if (!running) return; + if (!running) + return; running = false; closed = true; } - // This method should not be called from the main thread unless the shell is idle - // and can be cleaned up with (minimal) waiting. Only throw in debug mode. + // This method should not be called from the main thread unless the + // shell is idle and can be cleaned up with (minimal) waiting. Only + // throw in debug mode. if (!_idle && Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { Debug.log(ShellOnMainThreadException.EXCEPTION_NOT_IDLE); throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_NOT_IDLE); } - if (!_idle) waitForIdle(); + if (!_idle) + waitForIdle(); try { STDIN.write(("exit\n").getBytes("UTF-8")); STDIN.flush(); - // wait for our process to finish, while we gobble away in the background + // wait for our process to finish, while we gobble away in the + // background process.waitFor(); - // make sure our threads are done gobbling, our streams are closed, and the process is - // destroyed - while the latter two shouldn't be needed in theory, and may even produce - // warnings, in "normal" Java they are required for guaranteed cleanup of resources, so - // lets be safe and do this on Android as well - try { + // make sure our threads are done gobbling, our streams are + // closed, and the process is destroyed - while the latter two + // shouldn't be needed in theory, and may even produce warnings, + // in "normal" Java they are required for guaranteed cleanup of + // resources, so lets be safe and do this on Android as well + try { STDIN.close(); - } catch (IOException e) { + } catch (IOException e) { } STDOUT.join(); STDERR.join(); @@ -1218,21 +1599,21 @@ public class Shell { } /** - * Try to clean up as much as possible from a shell that's gotten itself wedged. - * Hopefully the StreamGobblers will croak on their own when the other side of - * the pipe is closed. + * Try to clean up as much as possible from a shell that's gotten itself + * wedged. Hopefully the StreamGobblers will croak on their own when the + * other side of the pipe is closed. */ public synchronized void kill() { running = false; closed = true; - try { + try { STDIN.close(); } catch (IOException e) { } try { process.destroy(); - } catch (Exception e) { + } catch (Exception e) { } } @@ -1242,12 +1623,15 @@ public class Shell { * @return Shell running ? */ public boolean isRunning() { + if (process == null) { + return false; + } try { // if this throws, we're still running - process.exitValue(); + process.exitValue(); return false; - } catch (IllegalThreadStateException e) { - } + } catch (IllegalThreadStateException e) { + } return true; } @@ -1259,31 +1643,42 @@ public class Shell { public synchronized boolean isIdle() { if (!isRunning()) { idle = true; - synchronized(idleSync) { + synchronized (idleSync) { idleSync.notifyAll(); - } + } } return idle; } /** - *

Wait for idle state. As this is a blocking call, you should not call it from the main UI thread. - * If you do so and debug mode is enabled, this method will intentionally crash your app.

- * - *

If not interrupted, this method will not return until all commands have finished executing. - * Note that this does not necessarily mean that all the callbacks have fired yet.

- * - *

If no Handler is used, all callbacks will have been executed when this method returns. If - * a Handler is used, and this method is called from a different thread than associated with the - * Handler's Looper, all callbacks will have been executed when this method returns as well. - * If however a Handler is used but this method is called from the same thread as associated - * with the Handler's Looper, there is no way to know.

- * - *

In practice this means that in most simple cases all callbacks will have completed when this - * method returns, but if you actually depend on this behavior, you should make certain this is - * indeed the case.

- * - *

See {@link Shell.Interactive} for further details on threading and handlers

+ *

+ * Wait for idle state. As this is a blocking call, you should not call + * it from the main UI thread. If you do so and debug mode is enabled, + * this method will intentionally crash your app. + *

+ *

+ * If not interrupted, this method will not return until all commands + * have finished executing. Note that this does not necessarily mean + * that all the callbacks have fired yet. + *

+ *

+ * If no Handler is used, all callbacks will have been executed when + * this method returns. If a Handler is used, and this method is called + * from a different thread than associated with the Handler's Looper, + * all callbacks will have been executed when this method returns as + * well. If however a Handler is used but this method is called from the + * same thread as associated with the Handler's Looper, there is no way + * to know. + *

+ *

+ * In practice this means that in most simple cases all callbacks will + * have completed when this method returns, but if you actually depend + * on this behavior, you should make certain this is indeed the case. + *

+ *

+ * See {@link Shell.Interactive} for further details on threading and + * handlers + *

* * @return True if wait complete, false if wait interrupted */ @@ -1297,28 +1692,27 @@ public class Shell { synchronized (idleSync) { while (!idle) { try { - idleSync.wait(); + idleSync.wait(); } catch (InterruptedException e) { return false; } } } - if ( - (handler != null) && - (handler.getLooper() != null) && - (handler.getLooper() != Looper.myLooper()) - ) { - // If the callbacks are posted to a different thread than this one, we can wait until - // all callbacks have called before returning. If we don't use a Handler at all, - // the callbacks are already called before we get here. If we do use a Handler but - // we use the same Looper, waiting here would actually block the callbacks from being - // called + if ((handler != null) && + (handler.getLooper() != null) && + (handler.getLooper() != Looper.myLooper())) { + // If the callbacks are posted to a different thread than + // this one, we can wait until all callbacks have called + // before returning. If we don't use a Handler at all, the + // callbacks are already called before we get here. If we do + // use a Handler but we use the same Looper, waiting here + // would actually block the callbacks from being called synchronized (callbackSync) { while (callbacks > 0) { try { - callbackSync.wait(); + callbackSync.wait(); } catch (InterruptedException e) { return false; } @@ -1336,7 +1730,7 @@ public class Shell { * @return Handler used ? */ public boolean hasHandler() { - return (handler != null); + return (handler != null); } } } diff --git a/src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java b/src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java index fe31c0f3..2a44083e 100644 --- a/src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java +++ b/src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java b/src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java index acb7764d..69732e3b 100644 --- a/src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java +++ b/src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java b/src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java index 04c4fe77..73052154 100644 --- a/src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java +++ b/src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma + * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala index 976da9ba..7d5a3a3b 100644 --- a/src/main/scala/com/github/shadowsocks/Shadowsocks.scala +++ b/src/main/scala/com/github/shadowsocks/Shadowsocks.scala @@ -341,6 +341,7 @@ class Shadowsocks ab.clear() ab.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`") ab.append("rm /data/data/com.github.shadowsocks/redsocks.conf") + ab.append("rm /data/data/com.github.shadowsocks/redsocks.pid") ab.append(Utils.getIptables + " -t nat -F OUTPUT") Console.runRootCommand(ab.toArray) diff --git a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala index cbfb886a..fd1ce794 100644 --- a/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala +++ b/src/main/scala/com/github/shadowsocks/ShadowsocksNatService.scala @@ -137,18 +137,27 @@ class ShadowsocksNatService extends Service with BaseService { if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) Core.sstunnel(cmd.toArray) } else { - val conf = { - ConfigUtils.PDNSD.format("127.0.0.1") - } + val cmdBuf = new ArrayBuffer[String] + cmdBuf += ("ss-tunnel" + , "-b", "127.0.0.1" + , "-l", "8163" + , "-L", "8.8.8.8:53" + , "-s", config.proxy + , "-p", config.remotePort.toString + , "-k", config.sitekey + , "-m", config.encMethod + , "-f", Path.BASE + "ss-tunnel.pid") + if (BuildConfig.DEBUG) Log.d(TAG, cmdBuf.mkString(" ")) + Core.sstunnel(cmdBuf.toArray) + + val conf = ConfigUtils + .PDNSD_BYPASS.format("127.0.0.1", getString(R.string.exclude), 8163) ConfigUtils.printToFile(new File(Path.BASE + "pdnsd.conf"))(p => { p.println(conf) }) - - val args = "pdnsd -c " + Path.BASE + "pdnsd.conf" - val cmd = Path.BASE + args - + val cmd = Path.BASE + "pdnsd -c " + Path.BASE + "pdnsd.conf" if (BuildConfig.DEBUG) Log.d(TAG, cmd) - Console.runRootCommand(cmd) + Core.pdnsd(cmd.split(" ")) } } @@ -172,7 +181,7 @@ class ShadowsocksNatService extends Service with BaseService { p.println(conf) }) val cmd = Path.BASE + args - Console.runRootCommand(cmd) + Console.runCommand(cmd) } /** Called when the activity is first created. */