From b3d188fbaecb8dee8897f9c6f279abb0558e172a Mon Sep 17 00:00:00 2001 From: Mygod Date: Sun, 29 Nov 2015 12:27:09 +0800 Subject: [PATCH] Update libsuperuser --- build.sbt | 1 + .../chainfire/libsuperuser/Application.java | 79 - .../java/eu/chainfire/libsuperuser/Debug.java | 242 --- .../libsuperuser/HideOverlaysReceiver.java | 59 - .../java/eu/chainfire/libsuperuser/Shell.java | 1736 ----------------- .../libsuperuser/ShellNotClosedException.java | 29 - .../ShellOnMainThreadException.java | 32 - .../chainfire/libsuperuser/StreamGobbler.java | 103 - 8 files changed, 1 insertion(+), 2280 deletions(-) delete mode 100644 src/main/java/eu/chainfire/libsuperuser/Application.java delete mode 100644 src/main/java/eu/chainfire/libsuperuser/Debug.java delete mode 100644 src/main/java/eu/chainfire/libsuperuser/HideOverlaysReceiver.java delete mode 100644 src/main/java/eu/chainfire/libsuperuser/Shell.java delete mode 100644 src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java delete mode 100644 src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java delete mode 100644 src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java diff --git a/build.sbt b/build.sbt index 5a83ff5c..1edc6161 100644 --- a/build.sbt +++ b/build.sbt @@ -30,6 +30,7 @@ libraryDependencies ++= Seq( "dnsjava" % "dnsjava" % "2.1.7", "com.github.kevinsawicki" % "http-request" % "6.0", "commons-net" % "commons-net" % "3.3", + "eu.chainfire" % "libsuperuser" % "1.0.0.201510071325", "com.google.zxing" % "android-integration" % "3.2.1", "net.glxn.qrgen" % "android" % "2.0", "com.google.android.gms" % "play-services-base" % "8.3.0", diff --git a/src/main/java/eu/chainfire/libsuperuser/Application.java b/src/main/java/eu/chainfire/libsuperuser/Application.java deleted file mode 100644 index a439517f..00000000 --- a/src/main/java/eu/chainfire/libsuperuser/Application.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.chainfire.libsuperuser; - -import android.content.Context; -import android.os.Handler; -import android.widget.Toast; - -/** - * Base application class to extend from, solving some issues with - * toasts and AsyncTasks you are likely to run into - */ -public class Application extends android.app.Application { - /** - * Shows a toast message - * - * @param context Any context belonging to this application - * @param message The message to show - */ - public static void toast(Context context, String message) { - // this is a static method so it is easier to call, - // as the context checking and casting is done for you - - if (context == null) return; - - if (!(context instanceof Application)) { - context = context.getApplicationContext(); - } - - if (context instanceof Application) { - final Context c = context; - final String m = message; - - ((Application)context).runInApplicationThread(new Runnable() { - @Override - public void run() { - Toast.makeText(c, m, Toast.LENGTH_LONG).show(); - } - }); - } - } - - private static Handler mApplicationHandler = new Handler(); - - /** - * Run a runnable in the main application thread - * - * @param r Runnable to run - */ - public void runInApplicationThread(Runnable r) { - mApplicationHandler.post(r); - } - - @Override - public void onCreate() { - super.onCreate(); - - try { - // workaround bug in AsyncTask, can show up (for example) when you toast from a service - // this makes sure AsyncTask's internal handler is created from the right (main) thread - Class.forName("android.os.AsyncTask"); - } catch (ClassNotFoundException e) { - } - } -} diff --git a/src/main/java/eu/chainfire/libsuperuser/Debug.java b/src/main/java/eu/chainfire/libsuperuser/Debug.java deleted file mode 100644 index 5b1e4381..00000000 --- a/src/main/java/eu/chainfire/libsuperuser/Debug.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.chainfire.libsuperuser; - -import android.os.Looper; -import android.util.Log; - -import com.github.shadowsocks.BuildConfig; - -/** - * Utility class for logging and debug features that (by default) does nothing when not in debug mode - */ -public class Debug { - - // ----- DEBUGGING ----- - - private static boolean debug = BuildConfig.DEBUG; - - /** - *

Enable or disable debug mode

- * - *

By default, debug mode is enabled for development - * builds and disabled for exported APKs - see - * BuildConfig.DEBUG

- * - * @param enabled Enable debug mode ? - */ - public static void setDebug(boolean enable) { - debug = enable; - } - - /** - *

Is debug mode enabled ?

- * - * @return Debug mode enabled - */ - public static boolean getDebug() { - return debug; - } - - // ----- LOGGING ----- - - public interface OnLogListener { - public void onLog(int type, String typeIndicator, String message); - } - - public static final String TAG = "libsuperuser"; - - public static final int LOG_GENERAL = 0x0001; - public static final int LOG_COMMAND = 0x0002; - public static final int LOG_OUTPUT = 0x0004; - - public static final int LOG_NONE = 0x0000; - public static final int LOG_ALL = 0xFFFF; - - private static int logTypes = LOG_ALL; - - private static OnLogListener logListener = null; - - /** - *

Log a message (internal)

- * - *

Current debug and enabled logtypes decide what gets logged - - * even if a custom callback is registered

- * - * @param type Type of message to log - * @param typeIndicator String indicator for message type - * @param message The message to log - */ - private static void logCommon(int type, String typeIndicator, String message) { - if (debug && ((logTypes & type) == type)) { - if (logListener != null) { - logListener.onLog(type, typeIndicator, message); - } else { - Log.d(TAG, "[" + TAG + "][" + typeIndicator + "]" + (!message.startsWith("[") && !message.startsWith(" ") ? " " : "") + message); - } - } - } - - /** - *

Log a "general" message

- * - *

These messages are infrequent and mostly occur at startup/shutdown or on error

- * - * @param message The message to log - */ - public static void log(String message) { - logCommon(LOG_GENERAL, "G", message); - } - - /** - *

Log a "per-command" message

- * - *

This could produce a lot of output if the client runs many commands in the session

- * - * @param message The message to log - */ - public static void logCommand(String message) { - logCommon(LOG_COMMAND, "C", message); - } - - /** - *

Log a line of stdout/stderr output

- * - *

This could produce a lot of output if the shell commands are noisy

- * - * @param message The message to log - */ - public static void logOutput(String message) { - logCommon(LOG_OUTPUT, "O", message); - } - - /** - *

Enable or disable logging specific types of message

- * - *

You may | (or) LOG_* constants together. Note that - * debug mode must also be enabled for actual logging to - * occur.

- * - * @param type LOG_* constants - * @param enabled Enable or disable - */ - public static void setLogTypeEnabled(int type, boolean enable) { - if (enable) { - logTypes |= type; - } else { - logTypes &= ~type; - } - } - - /** - *

Is logging for specific types of messages enabled ?

- * - *

You may | (or) LOG_* constants together, to learn if - * all passed message types are enabled for logging. Note - * that debug mode must also be enabled for actual logging - * to occur.

- * - * @param type LOG_* constants - */ - public static boolean getLogTypeEnabled(int type) { - return ((logTypes & type) == type); - } - - /** - *

Is logging for specific types of messages enabled ?

- * - *

You may | (or) LOG_* constants together, to learn if - * all message types are enabled for logging. Takes - * debug mode into account for the result.

- * - * @param type LOG_* constants - */ - public static boolean getLogTypeEnabledEffective(int type) { - return getDebug() && getLogTypeEnabled(type); - } - - /** - *

Register a custom log handler

- * - *

Replaces the log method (write to logcat) with your own - * handler. Whether your handler gets called is still dependent - * on debug mode and message types being enabled for logging.

- * - * @param onLogListener Custom log listener or NULL to revert to default - */ - public static void setOnLogListener(OnLogListener onLogListener) { - logListener = onLogListener; - } - - /** - *

Get the currently registered custom log handler

- * - * @return Current custom log handler or NULL if none is present - */ - public static OnLogListener getOnLogListener() { - return logListener; - } - - // ----- SANITY CHECKS ----- - - private static boolean sanityChecks = true; - - /** - *

Enable or disable sanity checks

- * - *

Enables or disables the library crashing when su is called - * from the main thread.

- * - * @param enabled Enable or disable - */ - public static void setSanityChecksEnabled(boolean enable) { - sanityChecks = enable; - } - - /** - *

Are sanity checks enabled ?

- * - *

Note that debug mode must also be enabled for actual - * sanity checks to occur.

- * - * @return True if enabled - */ - public static boolean getSanityChecksEnabled() { - return sanityChecks; - } - - /** - *

Are sanity checks enabled ?

- * - *

Takes debug mode into account for the result.

- * - * @return True if enabled - */ - public static boolean getSanityChecksEnabledEffective() { - return getDebug() && getSanityChecksEnabled(); - } - - /** - *

Are we running on the main thread ?

- * - * @return Running on main thread ? - */ - public static boolean onMainThread() { - return ((Looper.myLooper() != null) && (Looper.myLooper() == Looper.getMainLooper())); - } - -} diff --git a/src/main/java/eu/chainfire/libsuperuser/HideOverlaysReceiver.java b/src/main/java/eu/chainfire/libsuperuser/HideOverlaysReceiver.java deleted file mode 100644 index 4b4ce5a8..00000000 --- a/src/main/java/eu/chainfire/libsuperuser/HideOverlaysReceiver.java +++ /dev/null @@ -1,59 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.chainfire.libsuperuser; - -import android.content.BroadcastReceiver; -import android.content.Context; -import android.content.Intent; - -/** - *

- * Base receiver to extend to catch notifications when overlays should be - * hidden. - *

- *

- * Tapjacking protection in SuperSU prevents some dialogs from receiving user - * input when overlays are present. For security reasons this notification is - * only sent to apps that have previously been granted root access, so even if - * your app does not require root, you still need to request - * it, and the user must grant it. - *

- *

- * Note that the word overlay as used here should be interpreted as "any view or - * window possibly obscuring SuperSU dialogs". - *

- */ -public abstract class HideOverlaysReceiver extends BroadcastReceiver { - public static final String ACTION_HIDE_OVERLAYS = "eu.chainfire.supersu.action.HIDE_OVERLAYS"; - public static final String CATEGORY_HIDE_OVERLAYS = Intent.CATEGORY_INFO; - public static final String EXTRA_HIDE_OVERLAYS = "eu.chainfire.supersu.extra.HIDE"; - - @Override - public final void onReceive(Context context, Intent intent) { - if (intent.hasExtra(EXTRA_HIDE_OVERLAYS)) { - onHideOverlays(intent.getBooleanExtra(EXTRA_HIDE_OVERLAYS, false)); - } - } - - /** - * Called when overlays should be hidden or may be shown - * again. - * - * @param hide Should overlays be hidden? - */ - public abstract void onHideOverlays(boolean hide); -} diff --git a/src/main/java/eu/chainfire/libsuperuser/Shell.java b/src/main/java/eu/chainfire/libsuperuser/Shell.java deleted file mode 100644 index cf48036a..00000000 --- a/src/main/java/eu/chainfire/libsuperuser/Shell.java +++ /dev/null @@ -1,1736 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.chainfire.libsuperuser; - -import java.io.DataOutputStream; -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Locale; -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ScheduledThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -import android.os.Handler; -import android.os.Looper; - -import eu.chainfire.libsuperuser.StreamGobbler.OnLineListener; - -/** - * Class providing functionality to execute commands in a (root) 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. - *

- * - * @param shell The shell to use for executing the commands - * @param commands The commands to execute - * @param wantSTDERR Return STDERR in the output ? - * @return Output of the commands, or null in case of an error - */ - @Deprecated - public static List run(String shell, String[] commands, boolean wantSTDERR) { - return run(shell, commands, null, wantSTDERR); - } - - /** - *

- * 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 ? - * @return Output of the commands, or null in case of an error - */ - 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. - - Debug.log(ShellOnMainThreadException.EXCEPTION_COMMAND); - throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_COMMAND); - } - Debug.logCommand(String.format("[%s%%] START", shellUpper)); - - List res = Collections.synchronizedList(new ArrayList()); - - try { - // Combine passed environment with system environment - if (environment != null) { - Map newEnvironment = new HashMap(); - newEnvironment.putAll(System.getenv()); - int split; - for (String entry : environment) { - if ((split = entry.indexOf("=")) >= 0) { - newEnvironment.put(entry.substring(0, split), entry.substring(split + 1)); - } - } - int i = 0; - environment = new String[newEnvironment.size()]; - for (Map.Entry entry : newEnvironment.entrySet()) { - environment[i] = entry.getKey() + "=" + entry.getValue(); - i++; - } - } - - // 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); - - // start gobbling and write our commands to the shell - STDOUT.start(); - STDERR.start(); - for (String write : commands) { - Debug.logCommand(String.format("[%s+] %s", shellUpper, write)); - STDIN.write((write + "\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 - 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 { - STDIN.close(); - } catch (IOException e) { - } - STDOUT.join(); - STDERR.join(); - process.destroy(); - - // 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; - } catch (InterruptedException e) { - // this should really be re-thrown - res = null; - } - - Debug.logCommand(String.format("[%s%%] END", shell.toUpperCase(Locale.ENGLISH))); - return res; - } - - protected static String[] availableTestCommands = new String[] { - "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 - * @return true on success, false on error - */ - protected static boolean parseAvailableResult(List ret, boolean checkForRoot) { - if (ret == null) - return false; - - // this is only one of many ways this can be done - boolean echo_seen = false; - - for (String line : ret) { - if (line.contains("uid=")) { - // 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 - // native binaries - echo_seen = true; - } - } - - return echo_seen; - } - - /** - * This class provides utility functions to easily execute commands using SH - */ - public static class SH { - /** - * Runs command and return output - * - * @param command The command to run - * @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); - } - - /** - * Runs commands and return output - * - * @param commands The commands to run - * @return Output of the commands, or null in case of an error - */ - public static List run(List commands) { - return Shell.run("sh", commands.toArray(new String[commands.size()]), null, false); - } - - /** - * Runs commands and return output - * - * @param commands The commands to run - * @return Output of the commands, or null in case of an error - */ - public static List run(String[] commands) { - return Shell.run("sh", commands, null, false); - } - } - - /** - * This class provides utility functions to easily execute commands using SU - * (root shell), as well as detecting whether or not root is available, and - * 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 - */ - public static List run(String command) { - 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 - */ - public static List run(List commands) { - return Shell.run("su", commands.toArray(new String[commands.size()]), 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 - */ - 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 - * - * @return True if superuser access available - */ - public static boolean available() { - // this is only one of many ways this can be done - - List ret = run(Shell.availableTestCommands); - return Shell.parseAvailableResult(ret, true); - } - - /** - *

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

- * - * @param internal Request human-readable version or application - * internal version - * @return String containing the su version or null - */ - public static synchronized String version(boolean internal) { - int idx = internal ? 0 : 1; - if (suVersion[idx] == null) { - String version = null; - - 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 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 - */ - public static boolean isSU(String shell) { - // Strip parameters - int pos = shell.indexOf(' '); - if (pos >= 0) { - shell = shell.substring(0, pos); - } - - // Strip path - pos = shell.lastIndexOf('/'); - if (pos >= 0) { - shell = shell.substring(pos + 1); - } - - return shell.equals("su"); - } - - /** - * 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 - * @return Shell command - */ - public static String shell(int uid, String context) { - // su[ --context ][ ] - String shell = "su"; - - if ((context != null) && isSELinuxEnforcing()) { - 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); - } - } - - // Most su binaries support the "su " format, but in case - // they don't, lets skip it for the default 0 (root) case - if (uid > 0) { - shell = String.format(Locale.ENGLISH, "%s %d", shell, uid); - } - - 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; - } - } - - private interface OnResult { - // for any onCommandResult callback - public static final int WATCHDOG_EXIT = -1; - public static final int SHELL_DIED = -2; - - // for Interactive.open() callbacks only - public static final int SHELL_EXEC_FAILED = -3; - public static final int SHELL_WRONG_UID = -4; - 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 - */ - private static class Command { - private static int commandCounter = 0; - - 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, - OnCommandLineListener onCommandLineListener) { - this.commands = commands; - this.code = code; - this.onCommandResultListener = onCommandResultListener; - this.onCommandLineListener = onCommandLineListener; - this.marker = UUID.randomUUID().toString() + String.format("-%08x", ++commandCounter); - } - } - - /** - * Builder class for {@link Shell.Interactive} - */ - public static class Builder { - private Handler handler = null; - private boolean autoHandler = true; - private String shell = "sh"; - private boolean wantSTDERR = false; - private List commands = new LinkedList(); - private Map environment = new HashMap(); - private OnLineListener onSTDOUTLineListener = null; - private OnLineListener onSTDERRLineListener = null; - 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 - *

- * - * @param handler Handler to use - * @return This Builder object for method chaining - */ - 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 - *

- * - * @param autoHandler Auto-create handler ? - * @return This Builder object for method chaining - */ - 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 - * unless you have a good reason to - * - * @param shell Shell to use - * @return This Builder object for method chaining - */ - 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"); - } - - /** - * Convenience function to set "su" as used shell - * - * @return This Builder object for method chaining - */ - public Builder useSU() { - return setShell("su"); - } - - /** - * Set if error output should be appended to command block result output - * - * @param wantSTDERR Want error output ? - * @return This Builder object for method chaining - */ - public Builder setWantSTDERR(boolean wantSTDERR) { - this.wantSTDERR = wantSTDERR; - return this; - } - - /** - * Add or update an environment variable - * - * @param key Key of the environment variable - * @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; - } - - /** - * Add or update environment variables - * - * @param addEnvironment Map of environment variables - * @return This Builder object for method chaining - */ - public Builder addEnvironment(Map addEnvironment) { - environment.putAll(addEnvironment); - return this; - } - - /** - * Add a command to execute - * - * @param command Command to execute - * @return This Builder object for method chaining - */ - 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 - *

- * - * @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); - } - - /** - * Add commands to execute - * - * @param commands Commands to execute - * @return This Builder object for method chaining - */ - 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 - *

- * - * @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) - * @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); - } - - /** - * Add commands to execute - * - * @param commands Commands to execute - * @return This Builder object for method chaining - */ - 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 - *

- * - * @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) - * @return This Builder object for method chaining - */ - 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 - *

- * - * @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; - } - - /** - *

- * 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; - } - - /** - *

- * 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; - } - - /** - *

- * 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 - */ - public Builder setMinimalLogging(boolean useMinimal) { - Debug.setLogTypeEnabled(Debug.LOG_COMMAND | Debug.LOG_OUTPUT, !useMinimal); - return this; - } - - /** - * Construct a {@link Shell.Interactive} instance, and start the shell - */ - 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 - * - * @param onCommandResultListener Callback to return shell open status - */ - public Interactive open(OnCommandResultListener onCommandResultListener) { - return new Interactive(this, onCommandResultListener); - } - } - - /** - *

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

- */ - public static class Interactive { - private final Handler handler; - private final boolean autoHandler; - private final String shell; - private final boolean wantSTDERR; - private final List commands; - private final Map environment; - private final OnLineListener onSTDOUTLineListener; - private final OnLineListener onSTDERRLineListener; - private int watchdogTimeout; - - private Process process = null; - private DataOutputStream STDIN = null; - private StreamGobbler STDOUT = null; - private StreamGobbler STDERR = null; - private ScheduledThreadPoolExecutor watchdog = null; - - private volatile boolean running = false; - private volatile boolean idle = true; // read/write only synchronized - private volatile boolean closed = true; - private volatile int callbacks = 0; - private volatile int watchdogCount; - - private Object idleSync = new Object(); - private Object callbackSync = new Object(); - - private volatile int lastExitCode = 0; - private volatile String lastMarkerSTDOUT = null; - private volatile String lastMarkerSTDERR = null; - private volatile Command command = null; - private volatile List buffer = null; - - /** - * The only way to create an instance: Shell.Builder::open() - * - * @param builder Builder class to take values from - */ - private Interactive(final Builder builder, - final OnCommandResultListener onCommandResultListener) { - autoHandler = builder.autoHandler; - shell = builder.shell; - wantSTDERR = builder.wantSTDERR; - commands = builder.commands; - environment = builder.environment; - onSTDOUTLineListener = builder.onSTDOUTLineListener; - onSTDERRLineListener = builder.onSTDERRLineListener; - watchdogTimeout = builder.watchdogTimeout; - - // 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) { - handler = new Handler(); - } else { - handler = builder.handler; - } - - boolean ret = open(); - if (onCommandResultListener == null) { - return; - } else if (ret == false) { - onCommandResultListener.onCommandResult(0, - OnCommandResultListener.SHELL_EXEC_FAILED, null); - return; - } - - // 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() { - public void onCommandResult(int commandCode, int exitCode, List output) { - if (exitCode == OnCommandResultListener.SHELL_RUNNING && - Shell.parseAvailableResult(output, Shell.SU.isSU(shell)) != true) { - // shell is up, but it's brain-damaged - exitCode = OnCommandResultListener.SHELL_WRONG_UID; - } - watchdogTimeout = builder.watchdogTimeout; - onCommandResultListener.onCommandResult(0, exitCode, output); - } - }); - } - - @Override - protected void finalize() throws Throwable { - if (!closed && Debug.getSanityChecksEnabledEffective()) { - // waste of resources - Debug.log(ShellNotClosedException.EXCEPTION_NOT_CLOSED); - throw new ShellNotClosedException(); - } - super.finalize(); - } - - /** - * Add a command to execute - * - * @param command Command to execute - */ - 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 - *

- * - * @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); - } - - /** - *

- * 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, (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 - *

- * - * @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) - */ - 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, (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 - *

- * - * @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) - */ - public synchronized void addCommand(String[] commands, int code, - OnCommandResultListener onCommandResultListener) { - this.commands.add(new Command(commands, code, onCommandResultListener, null)); - runNextCommand(); - } - - /** - *

- * 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 synchronized void addCommand(String[] commands, int code, - OnCommandLineListener onCommandLineListener) { - this.commands.add(new Command(commands, code, null, onCommandLineListener)); - runNextCommand(); - } - - /** - * 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 (!isRunning()) { - exitCode = OnCommandResultListener.SHELL_DIED; - Debug.log(String.format("[%s%%] SHELL_DIED", shell.toUpperCase(Locale.ENGLISH))); - } else if (watchdogCount++ < watchdogTimeout) { - return; - } else { - exitCode = OnCommandResultListener.WATCHDOG_EXIT; - Debug.log(String.format("[%s%%] WATCHDOG_EXIT", shell.toUpperCase(Locale.ENGLISH))); - } - - if (handler != null) { - postCallback(command, exitCode, buffer); - } - - // prevent multiple callbacks for the same command - command = null; - buffer = null; - idle = true; - - watchdog.shutdown(); - watchdog = null; - kill(); - } - - /** - * Start the periodic timer when a command is submitted - */ - private void startWatchdog() { - if (watchdogTimeout == 0) { - return; - } - watchdogCount = 0; - watchdog = new ScheduledThreadPoolExecutor(1); - watchdog.scheduleAtFixedRate(new Runnable() { - @Override - public void run() { - handleWatchdog(); - } - }, 1, 1, TimeUnit.SECONDS); - } - - /** - * Disable the watchdog timer upon command completion - */ - private void stopWatchdog() { - if (watchdog != null) { - watchdog.shutdownNow(); - watchdog = null; - } - } - - /** - * Run the next command if any and if ready - * - * @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 && (commands.size() > 0)) { - Command command = commands.get(0); - commands.remove(0); - - buffer = null; - lastExitCode = 0; - lastMarkerSTDOUT = 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()); - } - - 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")); - } - STDIN.write(("echo " + command.marker + " $?\n").getBytes("UTF-8")); - STDIN.write(("echo " + command.marker + " >&2\n").getBytes("UTF-8")); - STDIN.flush(); - } catch (IOException e) { - } - } else { - runNextCommand(false); - } - } else if (!running) { - // our shell died for unknown reasons - abort all submissions - while (commands.size() > 0) { - postCallback(commands.remove(0), OnCommandResultListener.SHELL_DIED, null); - } - } - - if (idle && notifyIdle) { - synchronized (idleSync) { - idleSync.notifyAll(); - } - } - } - - /** - * Processes a STDOUT/STDERR line containing an end/exitCode marker - */ - private synchronized void processMarker() { - if (command.marker.equals(lastMarkerSTDOUT) - && (command.marker.equals(lastMarkerSTDERR))) { - postCallback(command, lastExitCode, buffer); - stopWatchdog(); - command = null; - buffer = null; - idle = true; - runNextCommand(); - } - } - - /** - * Process a normal STDOUT/STDERR line - * - * @param line Line to process - * @param listener Callback to call or null - */ - private synchronized void processLine(String line, OnLineListener listener) { - if (listener != null) { - if (handler != null) { - final String fLine = line; - final OnLineListener fListener = listener; - - startCallback(); - handler.post(new Runnable() { - @Override - public void run() { - try { - fListener.onLine(fLine); - } finally { - endCallback(); - } - } - }); - } else { - listener.onLine(line); - } - } - } - - /** - * Add line to internal buffer - * - * @param line Line to add - */ - private synchronized void addBuffer(String line) { - if (buffer != null) { - buffer.add(line); - } - } - - /** - * Increase callback counter - */ - private void startCallback() { - synchronized (callbackSync) { - callbacks++; - } - } - - /** - * 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 && fCommand.onCommandLineListener == null) { - return; - } - if (handler == null) { - 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(); - handler.post(new Runnable() { - @Override - public void run() { - try { - 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(); - } - } - }); - } - - /** - * Decrease callback counter, signals callback complete state when - * dropped to 0 - */ - private void endCallback() { - synchronized (callbackSync) { - callbacks--; - if (callbacks == 0) { - callbackSync.notifyAll(); - } - } - } - - /** - * Internal call that launches the shell, starts gobbling, and starts - * executing commands. See {@link Shell.Interactive} - * - * @return Opened successfully ? - */ - private synchronized boolean open() { - Debug.log(String.format("[%s%%] START", shell.toUpperCase(Locale.ENGLISH))); - - try { - // setup our process, retrieve STDIN stream, and STDOUT/STDERR - // gobblers - if (environment.size() == 0) { - process = Runtime.getRuntime().exec(shell); - } else { - Map newEnvironment = new HashMap(); - newEnvironment.putAll(System.getenv()); - newEnvironment.putAll(environment); - int i = 0; - String[] env = new String[newEnvironment.size()]; - for (Map.Entry entry : newEnvironment.entrySet()) { - env[i] = entry.getKey() + "=" + entry.getValue(); - 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) { - } - lastMarkerSTDOUT = command.marker; - processMarker(); - } else { - addBuffer(line); - processLine(line, onSTDOUTLineListener); - processLine(line, command.onCommandLineListener); - } - } - } - }); - 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); - } - } - } - }); - - // start gobbling and write our commands to the shell - STDOUT.start(); - STDERR.start(); - - running = true; - closed = false; - - runNextCommand(); - - return true; - } catch (IOException e) { - // shell probably not found - return false; - } - } - - /** - * 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; - 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. - if (!_idle && Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { - Debug.log(ShellOnMainThreadException.EXCEPTION_NOT_IDLE); - throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_NOT_IDLE); - } - - 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 - 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 { - STDIN.close(); - } catch (IOException e) { - } - STDOUT.join(); - STDERR.join(); - stopWatchdog(); - process.destroy(); - } catch (IOException e) { - // shell probably not found - } catch (InterruptedException e) { - // this should really be re-thrown - } - - Debug.log(String.format("[%s%%] END", shell.toUpperCase(Locale.ENGLISH))); - } - - /** - * 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 { - STDIN.close(); - } catch (IOException e) { - } - try { - process.destroy(); - } catch (Exception e) { - } - } - - /** - * Is our shell still running ? - * - * @return Shell running ? - */ - public boolean isRunning() { - if (process == null) { - return false; - } - try { - // if this throws, we're still running - process.exitValue(); - return false; - } catch (IllegalThreadStateException e) { - } - return true; - } - - /** - * Have all commands completed executing ? - * - * @return Shell idle ? - */ - public synchronized boolean isIdle() { - if (!isRunning()) { - idle = true; - 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 - *

- * - * @return True if wait complete, false if wait interrupted - */ - public boolean waitForIdle() { - if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { - Debug.log(ShellOnMainThreadException.EXCEPTION_WAIT_IDLE); - throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_WAIT_IDLE); - } - - if (isRunning()) { - synchronized (idleSync) { - while (!idle) { - try { - 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 - - synchronized (callbackSync) { - while (callbacks > 0) { - try { - callbackSync.wait(); - } catch (InterruptedException e) { - return false; - } - } - } - } - } - - return true; - } - - /** - * Are we using a Handler to post callbacks ? - * - * @return Handler used ? - */ - public boolean hasHandler() { - return (handler != null); - } - } -} diff --git a/src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java b/src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java deleted file mode 100644 index 2a44083e..00000000 --- a/src/main/java/eu/chainfire/libsuperuser/ShellNotClosedException.java +++ /dev/null @@ -1,29 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.chainfire.libsuperuser; - -/** - * Exception class used to notify developer that a shell was not close()d - */ -@SuppressWarnings("serial") -public class ShellNotClosedException extends RuntimeException { - public static final String EXCEPTION_NOT_CLOSED = "Application did not close() interactive shell"; - - public ShellNotClosedException() { - super(EXCEPTION_NOT_CLOSED); - } -} diff --git a/src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java b/src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java deleted file mode 100644 index 69732e3b..00000000 --- a/src/main/java/eu/chainfire/libsuperuser/ShellOnMainThreadException.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.chainfire.libsuperuser; - -/** - * Exception class used to crash application when shell commands are executed - * from the main thread, and we are in debug mode. - */ -@SuppressWarnings("serial") -public class ShellOnMainThreadException extends RuntimeException { - public static final String EXCEPTION_COMMAND = "Application attempted to run a shell command from the main thread"; - public static final String EXCEPTION_NOT_IDLE = "Application attempted to wait for a non-idle shell to close on the main thread"; - public static final String EXCEPTION_WAIT_IDLE = "Application attempted to wait for a shell to become idle on the main thread"; - - public ShellOnMainThreadException(String message) { - super(message); - } -} diff --git a/src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java b/src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java deleted file mode 100644 index 73052154..00000000 --- a/src/main/java/eu/chainfire/libsuperuser/StreamGobbler.java +++ /dev/null @@ -1,103 +0,0 @@ -/* - * 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. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package eu.chainfire.libsuperuser; - -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.util.List; - -/** - * Thread utility class continuously reading from an InputStream - */ -public class StreamGobbler extends Thread { - /** - * Line callback interface - */ - public interface OnLineListener { - /** - *

Line callback

- * - *

This callback should process the line as quickly as possible. - * Delays in this callback may pause the native process or even - * result in a deadlock

- * - * @param line String that was gobbled - */ - public void onLine(String line); - } - - private String shell = null; - private BufferedReader reader = null; - private List writer = null; - private OnLineListener listener = null; - - /** - *

StreamGobbler constructor

- * - *

We use this class because shell STDOUT and STDERR should be read as quickly as - * possible to prevent a deadlock from occurring, or Process.waitFor() never - * returning (as the buffer is full, pausing the native process)

- * - * @param shell Name of the shell - * @param inputStream InputStream to read from - * @param outputList List to write to, or null - */ - public StreamGobbler(String shell, InputStream inputStream, List outputList) { - this.shell = shell; - reader = new BufferedReader(new InputStreamReader(inputStream)); - writer = outputList; - } - - /** - *

StreamGobbler constructor

- * - *

We use this class because shell STDOUT and STDERR should be read as quickly as - * possible to prevent a deadlock from occurring, or Process.waitFor() never - * returning (as the buffer is full, pausing the native process)

- * - * @param shell Name of the shell - * @param inputStream InputStream to read from - * @param onLineListener OnLineListener callback - */ - public StreamGobbler(String shell, InputStream inputStream, OnLineListener onLineListener) { - this.shell = shell; - reader = new BufferedReader(new InputStreamReader(inputStream)); - listener = onLineListener; - } - - @Override - public void run() { - // keep reading the InputStream until it ends (or an error occurs) - try { - String line = null; - while ((line = reader.readLine()) != null) { - Debug.logOutput(String.format("[%s] %s", shell, line)); - if (writer != null) writer.add(line); - if (listener != null) listener.onLine(line); - } - } catch (IOException e) { - } - - // make sure our stream is closed and resources will be freed - try { - reader.close(); - } catch (IOException e) { - } - } -}