Commit 27ba7d64 authored by Max Lv's avatar Max Lv

fix a very old bug in redsocks

parent 8fb5d7f8
...@@ -780,7 +780,7 @@ static int redsocks_init_instance(redsocks_instance *instance) ...@@ -780,7 +780,7 @@ static int redsocks_init_instance(redsocks_instance *instance)
int on = 1; int on = 1;
int fd = -1; int fd = -1;
fd = socket(AF_INET, SOCK_STREAM, 0); fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd == -1) { if (fd == -1) {
log_errno(LOG_ERR, "socket"); log_errno(LOG_ERR, "socket");
goto fail; goto fail;
......
/* /*
* Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
......
/* /*
* Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -18,6 +18,7 @@ package eu.chainfire.libsuperuser; ...@@ -18,6 +18,7 @@ package eu.chainfire.libsuperuser;
import android.os.Looper; import android.os.Looper;
import android.util.Log; import android.util.Log;
import com.github.shadowsocks.BuildConfig; import com.github.shadowsocks.BuildConfig;
/** /**
......
/* /*
* Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -38,20 +38,23 @@ import android.os.Looper; ...@@ -38,20 +38,23 @@ import android.os.Looper;
import eu.chainfire.libsuperuser.StreamGobbler.OnLineListener; 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 {
/** /**
* <p>Runs commands using the supplied shell, and returns the output, or null in * <p>
* case of errors.</p> * Runs commands using the supplied shell, and returns the output, or null
* * in case of errors.
* <p>This method is deprecated and only provided for backwards compatibility. * </p>
* Use {@link #run(String, String[], String[], boolean)} instead, and see that * <p>
* same method for usage notes.</p> * 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.
* </p>
* *
* @param shell The shell to use for executing the commands * @param shell The shell to use for executing the commands
* @param commands The commands to execute * @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 * @return Output of the commands, or null in case of an error
*/ */
@Deprecated @Deprecated
...@@ -60,38 +63,48 @@ public class Shell { ...@@ -60,38 +63,48 @@ public class Shell {
} }
/** /**
* <p>Runs commands using the supplied shell, and returns the output, or null in * <p>
* case of errors.</p> * Runs commands using the supplied shell, and returns the output, or null
* * in case of errors.
* <p>Note that due to compatibility with older Android versions, * </p>
* wantSTDERR is not implemented using redirectErrorStream, but rather appended * <p>
* to the output. STDOUT and STDERR are thus not guaranteed to be in the correct * Note that due to compatibility with older Android versions, wantSTDERR is
* order in the output.</p> * not implemented using redirectErrorStream, but rather appended to the
* * output. STDOUT and STDERR are thus not guaranteed to be in the correct
* <p>Note as well that this code will intentionally crash when run in debug mode * order in the output.
* from the main thread of the application. You should always execute shell * </p>
* commands from a background thread.</p> * <p>
* * Note as well that this code will intentionally crash when run in debug
* <p>When in debug mode, the code will also excessively log the commands passed to * mode from the main thread of the application. You should always execute
* and the output returned from the shell.</p> * shell commands from a background thread.
* * </p>
* <p>Though this function uses background threads to gobble STDOUT and STDERR so * <p>
* a deadlock does not occur if the shell produces massive output, the output is * When in debug mode, the code will also excessively log the commands
* still stored in a List&lt;String&gt;, and as such doing something like <em>'ls -lR /'</em> * passed to and the output returned from the shell.
* will probably have you run out of memory.</p> * </p>
* <p>
* 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&lt;String&gt;, and as such doing
* something like <em>'ls -lR /'</em> will probably have you run out of
* memory.
* </p>
* *
* @param shell The shell to use for executing the commands * @param shell The shell to use for executing the commands
* @param commands The commands to execute * @param commands The commands to execute
* @param environment List of all environment variables (in 'key=value' format) or null for defaults * @param environment List of all environment variables (in 'key=value'
* @param wantSTDERR Return STDERR in the output ? * format) or null for defaults
* @param wantSTDERR Return STDERR in the output ?
* @return Output of the commands, or null in case of an error * @return Output of the commands, or null in case of an error
*/ */
public static List<String> run(String shell, String[] commands, String[] environment, boolean wantSTDERR) { public static List<String> run(String shell, String[] commands, String[] environment,
boolean wantSTDERR) {
String shellUpper = shell.toUpperCase(Locale.ENGLISH); String shellUpper = shell.toUpperCase(Locale.ENGLISH);
if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { if (Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) {
// check if we're running in the main thread, and if so, crash if we're in debug mode, // check if we're running in the main thread, and if so, crash if
// to let the developer know attention is needed here. // we're in debug mode, to let the developer know attention is
// needed here.
Debug.log(ShellOnMainThreadException.EXCEPTION_COMMAND); Debug.log(ShellOnMainThreadException.EXCEPTION_COMMAND);
throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_COMMAND); throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_COMMAND);
...@@ -119,11 +132,14 @@ public class Shell { ...@@ -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); Process process = Runtime.getRuntime().exec(shell, environment);
DataOutputStream STDIN = new DataOutputStream(process.getOutputStream()); DataOutputStream STDIN = new DataOutputStream(process.getOutputStream());
StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(), res); StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.getInputStream(),
StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(), wantSTDERR ? res : null); res);
StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.getErrorStream(),
wantSTDERR ? res : null);
// start gobbling and write our commands to the shell // start gobbling and write our commands to the shell
STDOUT.start(); STDOUT.start();
...@@ -133,17 +149,25 @@ public class Shell { ...@@ -133,17 +149,25 @@ public class Shell {
STDIN.write((write + "\n").getBytes("UTF-8")); STDIN.write((write + "\n").getBytes("UTF-8"));
STDIN.flush(); STDIN.flush();
} }
STDIN.write("exit\n".getBytes("UTF-8")); try {
STDIN.flush(); 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(); process.waitFor();
// make sure our threads are done gobbling, our streams are closed, and the process is // make sure our threads are done gobbling, our streams are closed,
// destroyed - while the latter two shouldn't be needed in theory, and may even produce // and the process is destroyed - while the latter two shouldn't be
// warnings, in "normal" Java they are required for guaranteed cleanup of resources, so // needed in theory, and may even produce warnings, in "normal" Java
// lets be safe and do this on Android as well // they are required for guaranteed cleanup of resources, so lets be
try { // safe and do this on Android as well
try {
STDIN.close(); STDIN.close();
} catch (IOException e) { } catch (IOException e) {
} }
...@@ -154,7 +178,7 @@ public class Shell { ...@@ -154,7 +178,7 @@ public class Shell {
// in case of su, 255 usually indicates access denied // in case of su, 255 usually indicates access denied
if (SU.isSU(shell) && (process.exitValue() == 255)) { if (SU.isSU(shell) && (process.exitValue() == 255)) {
res = null; res = null;
} }
} catch (IOException e) { } catch (IOException e) {
// shell probably not found // shell probably not found
res = null; res = null;
...@@ -168,19 +192,21 @@ public class Shell { ...@@ -168,19 +192,21 @@ public class Shell {
} }
protected static String[] availableTestCommands = new String[] { protected static String[] availableTestCommands = new String[] {
"echo -BOC-", "echo -BOC-",
"id" "id"
}; };
/** /**
* See if the shell is alive, and if so, check the UID * See if the shell is alive, and if so, check the UID
* *
* @param ret Standard output from running availableTestCommands * @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 * @return true on success, false on error
*/ */
protected static boolean parseAvailableResult(List<String> ret, boolean checkForRoot) { protected static boolean parseAvailableResult(List<String> ret, boolean checkForRoot) {
if (ret == null) return false; if (ret == null)
return false;
// this is only one of many ways this can be done // this is only one of many ways this can be done
boolean echo_seen = false; boolean echo_seen = false;
...@@ -190,8 +216,10 @@ public class Shell { ...@@ -190,8 +216,10 @@ public class Shell {
// id command is working, let's see if we are actually root // id command is working, let's see if we are actually root
return !checkForRoot || line.contains("uid=0"); return !checkForRoot || line.contains("uid=0");
} else if (line.contains("-BOC-")) { } else if (line.contains("-BOC-")) {
// if we end up here, at least the su command starts some kind of shell, // if we end up here, at least the su command starts some kind
// let's hope it has root privileges - no way to know without additional // of shell,
// let's hope it has root privileges - no way to know without
// additional
// native binaries // native binaries
echo_seen = true; echo_seen = true;
} }
...@@ -211,7 +239,9 @@ public class Shell { ...@@ -211,7 +239,9 @@ public class Shell {
* @return Output of the command, or null in case of an error * @return Output of the command, or null in case of an error
*/ */
public static List<String> run(String command) { public static List<String> 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 { ...@@ -241,21 +271,30 @@ public class Shell {
* if so which version. * if so which version.
*/ */
public static class SU { 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 * Runs command as root (if available) and return output
* *
* @param command The command to run * @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<String> run(String command) { public static List<String> 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 * Runs commands as root (if available) and return output
* *
* @param commands The commands to run * @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<String> run(List<String> commands) { public static List<String> run(List<String> commands) {
return Shell.run("su", commands.toArray(new String[commands.size()]), null, false); return Shell.run("su", commands.toArray(new String[commands.size()]), null, false);
...@@ -265,15 +304,17 @@ public class Shell { ...@@ -265,15 +304,17 @@ public class Shell {
* Runs commands as root (if available) and return output * Runs commands as root (if available) and return output
* *
* @param commands The commands to run * @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<String> run(String[] commands) { public static List<String> run(String[] commands) {
return Shell.run("su", commands, null, false); return Shell.run("su", commands, null, false);
} }
/** /**
* Detects whether or not superuser access is available, by checking the output * Detects whether or not superuser access is available, by checking the
* of the "id" command if available, checking if a shell runs at all otherwise * output of the "id" command if available, checking if a shell runs at
* all otherwise
* *
* @return True if superuser access available * @return True if superuser access available
*/ */
...@@ -285,41 +326,65 @@ public class Shell { ...@@ -285,41 +326,65 @@ public class Shell {
} }
/** /**
* <p>Detects the version of the su binary installed (if any), if supported by the binary. * <p>
* Most binaries support two different version numbers, the public version that is * Detects the version of the su binary installed (if any), if supported
* displayed to users, and an internal version number that is used for version number * by the binary. Most binaries support two different version numbers,
* comparisons. Returns null if su not available or retrieving the version isn't supported.</p> * the public version that is displayed to users, and an internal
* * version number that is used for version number comparisons. Returns
* <p>Note that su binary version and GUI (APK) version can be completely different.</p> * null if su not available or retrieving the version isn't supported.
* * </p>
* @param internal Request human-readable version or application internal version * <p>
* Note that su binary version and GUI (APK) version can be completely
* different.
* </p>
* <p>
* This function caches its result to improve performance on multiple
* calls
* </p>
*
* @param internal Request human-readable version or application
* internal version
* @return String containing the su version or null * @return String containing the su version or null
*/ */
public static String version(boolean internal) { public static synchronized String version(boolean internal) {
List<String> ret = Shell.run( int idx = internal ? 0 : 1;
internal ? "su -V" : "su -v", if (suVersion[idx] == null) {
new String[] { }, String version = null;
null,
false List<String> ret = Shell.run(
); internal ? "su -V" : "su -v",
if (ret == null) return null; new String[] {},
null,
for (String line : ret) { false
if (!internal) { );
if (line.contains(".")) return line;
} else { if (ret != null) {
try { for (String line : ret) {
if (Integer.parseInt(line) > 0) return line; if (!internal) {
} catch(NumberFormatException e) { 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 * Attempts to deduce if the shell command refers to a su shell
* *
* @param shell Shell command to run * @param shell Shell command to run
* @return Shell command appears to be su * @return Shell command appears to be su
*/ */
...@@ -340,9 +405,9 @@ public class Shell { ...@@ -340,9 +405,9 @@ public class Shell {
} }
/** /**
* Constructs a shell command to start a su shell using the supplied * Constructs a shell command to start a su shell using the supplied uid
* uid and SELinux context. This is can be an expensive operation, * and SELinux context. This is can be an expensive operation, consider
* consider caching the result. * caching the result.
* *
* @param uid Uid to use (0 == root) * @param uid Uid to use (0 == root)
* @param context (SELinux) context name to use or null * @param context (SELinux) context name to use or null
...@@ -352,47 +417,16 @@ public class Shell { ...@@ -352,47 +417,16 @@ public class Shell {
// su[ --context <context>][ <uid>] // su[ --context <context>][ <uid>]
String shell = "su"; String shell = "su";
// First known firmware with SELinux built-in was a 4.2 (17) leak if ((context != null) && isSELinuxEnforcing()) {
if ((context != null) && (android.os.Build.VERSION.SDK_INT >= 17)) { String display = version(false);
Boolean enforcing = null; 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 // We only know the format for SuperSU v1.90+ right now
// useful aside from audit testing, but we want to avoid if ((display != null) &&
// switching the context due to the increased chance of issues. (internal != null) &&
if (enforcing) { (display.endsWith("SUPERSU")) &&
String display = version(false); (Integer.valueOf(internal) >= 190)) {
String internal = version(true); 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 { ...@@ -404,29 +438,84 @@ public class Shell {
return shell; return shell;
} }
}
/**
* 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 {
/** /**
* <p>Command result callback</p> * 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
* <p>Depending on how and on which thread the shell was created, this callback * SELinux enforcing mode)
* 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</p>
* *
* <p>See {@link Shell.Interactive} for threading details</p> * @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
* *
* @param commandCode Value previously supplied to addCommand * @return true if SELinux set to enforcing, or false in the case of
* @param exitCode Exit code of the last command in the block * permissive or not present
* @param output All output generated by the command block
*/ */
public void onCommandResult(int commandCode, int exitCode, List<String> output); 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;
}
/**
* <p>
* Clears results cached by isSELinuxEnforcing() and version(boolean
* internal) calls.
* </p>
* <p>
* 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.
* </p>
*/
public static synchronized void clearCachedResults() {
isSELinuxEnforcing = null;
suVersion[0] = null;
suVersion[1] = null;
}
}
private interface OnResult {
// for any onCommandResult callback // for any onCommandResult callback
public static final int WATCHDOG_EXIT = -1; public static final int WATCHDOG_EXIT = -1;
public static final int SHELL_DIED = -2; public static final int SHELL_DIED = -2;
...@@ -437,6 +526,60 @@ public class Shell { ...@@ -437,6 +526,60 @@ public class Shell {
public static final int SHELL_RUNNING = 0; 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 {
/**
* <p>
* Command result callback
* </p>
* <p>
* 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
* </p>
* <p>
* See {@link Shell.Interactive} for threading details
* </p>
*
* @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<String> 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 {
/**
* <p>
* Command result callback
* </p>
* <p>
* 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
* </p>
* <p>
* See {@link Shell.Interactive} for threading details
* </p>
*
* @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 * Internal class to store command block properties
*/ */
...@@ -446,12 +589,16 @@ public class Shell { ...@@ -446,12 +589,16 @@ public class Shell {
private final String[] commands; private final String[] commands;
private final int code; private final int code;
private final OnCommandResultListener onCommandResultListener; private final OnCommandResultListener onCommandResultListener;
private final OnCommandLineListener onCommandLineListener;
private final String marker; 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.commands = commands;
this.code = code; this.code = code;
this.onCommandResultListener = onCommandResultListener; this.onCommandResultListener = onCommandResultListener;
this.onCommandLineListener = onCommandLineListener;
this.marker = UUID.randomUUID().toString() + String.format("-%08x", ++commandCounter); this.marker = UUID.randomUUID().toString() + String.format("-%08x", ++commandCounter);
} }
} }
...@@ -471,24 +618,38 @@ public class Shell { ...@@ -471,24 +618,38 @@ public class Shell {
private int watchdogTimeout = 0; private int watchdogTimeout = 0;
/** /**
* <p>Set a custom handler that will be used to post all callbacks to</p> * <p>
* * Set a custom handler that will be used to post all callbacks to
* <p>See {@link Shell.Interactive} for further details on threading and handlers</p> * </p>
* <p>
* See {@link Shell.Interactive} for further details on threading and
* handlers
* </p>
* *
* @param handler Handler to use * @param handler Handler to use
* @return This Builder object for method chaining * @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;
}
/** /**
* <p>Automatically create a handler if possible ? Default to true</p> * <p>
* * Automatically create a handler if possible ? Default to true
* <p>See {@link Shell.Interactive} for further details on threading and handlers</p> * </p>
* <p>
* See {@link Shell.Interactive} for further details on threading and
* handlers
* </p>
* *
* @param autoHandler Auto-create handler ? * @param autoHandler Auto-create handler ?
* @return This Builder object for method chaining * @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 * Set shell binary to use. Usually "sh" or "su", do not use a full path
...@@ -497,21 +658,28 @@ public class Shell { ...@@ -497,21 +658,28 @@ public class Shell {
* @param shell Shell to use * @param shell Shell to use
* @return This Builder object for method chaining * @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 * Convenience function to set "sh" as used shell
* *
* @return This Builder object for method chaining * @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 * Convenience function to set "su" as used shell
* *
* @return This Builder object for method chaining * @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 * Set if error output should be appended to command block result output
...@@ -519,7 +687,10 @@ public class Shell { ...@@ -519,7 +687,10 @@ public class Shell {
* @param wantSTDERR Want error output ? * @param wantSTDERR Want error output ?
* @return This Builder object for method chaining * @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 * Add or update an environment variable
...@@ -528,7 +699,10 @@ public class Shell { ...@@ -528,7 +699,10 @@ public class Shell {
* @param value Value of the environment variable * @param value Value of the environment variable
* @return This Builder object for method chaining * @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 * Add or update environment variables
...@@ -536,7 +710,10 @@ public class Shell { ...@@ -536,7 +710,10 @@ public class Shell {
* @param addEnvironment Map of environment variables * @param addEnvironment Map of environment variables
* @return This Builder object for method chaining * @return This Builder object for method chaining
*/ */
public Builder addEnvironment(Map<String, String> addEnvironment) { environment.putAll(addEnvironment); return this; } public Builder addEnvironment(Map<String, String> addEnvironment) {
environment.putAll(addEnvironment);
return this;
}
/** /**
* Add a command to execute * Add a command to execute
...@@ -544,19 +721,30 @@ public class Shell { ...@@ -544,19 +721,30 @@ public class Shell {
* @param command Command to execute * @param command Command to execute
* @return This Builder object for method chaining * @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);
}
/** /**
* <p>Add a command to execute, with a callback to be called on completion</p> * <p>
* * Add a command to execute, with a callback to be called on completion
* <p>The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details</p> * </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
* *
* @param command Command to execute * @param command Command to execute
* @param code User-defined value passed back to the callback * @param code User-defined value passed back to the callback
* @param onCommandResultListener Callback to be called on completion * @param onCommandResultListener Callback to be called on completion
* @return This Builder object for method chaining * @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 * Add commands to execute
...@@ -564,19 +752,31 @@ public class Shell { ...@@ -564,19 +752,31 @@ public class Shell {
* @param commands Commands to execute * @param commands Commands to execute
* @return This Builder object for method chaining * @return This Builder object for method chaining
*/ */
public Builder addCommand(List<String> commands) { return addCommand(commands, 0, null); } public Builder addCommand(List<String> commands) {
return addCommand(commands, 0, null);
}
/** /**
* <p>Add commands to execute, with a callback to be called on completion (of all commands)</p> * <p>
* * Add commands to execute, with a callback to be called on completion
* <p>The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details</p> * (of all commands)
* </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
* *
* @param commands Commands to execute * @param commands Commands to execute
* @param code User-defined value passed back to the callback * @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 * @return This Builder object for method chaining
*/ */
public Builder addCommand(List<String> commands, int code, OnCommandResultListener onCommandResultListener) { return addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); } public Builder addCommand(List<String> commands, int code,
OnCommandResultListener onCommandResultListener) {
return addCommand(commands.toArray(new String[commands.size()]), code,
onCommandResultListener);
}
/** /**
* Add commands to execute * Add commands to execute
...@@ -584,58 +784,96 @@ public class Shell { ...@@ -584,58 +784,96 @@ public class Shell {
* @param commands Commands to execute * @param commands Commands to execute
* @return This Builder object for method chaining * @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);
}
/** /**
* <p>Add commands to execute, with a callback to be called on completion (of all commands)</p> * <p>
* * Add commands to execute, with a callback to be called on completion
* <p>The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details</p> * (of all commands)
* </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
* *
* @param commands Commands to execute * @param commands Commands to execute
* @param code User-defined value passed back to the callback * @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 * @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;
}
/** /**
* <p>Set a callback called for every line output to STDOUT by the shell</p> * <p>
* * Set a callback called for every line output to STDOUT by the shell
* <p>The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details</p> * </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
* *
* @param onLineListener Callback to be called for each line * @param onLineListener Callback to be called for each line
* @return This Builder object for method chaining * @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;
}
/** /**
* <p>Set a callback called for every line output to STDERR by the shell</p> * <p>
* * Set a callback called for every line output to STDERR by the shell
* <p>The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details</p> * </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
* *
* @param onLineListener Callback to be called for each line * @param onLineListener Callback to be called for each line
* @return This Builder object for method chaining * @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;
}
/** /**
* <p>Enable command timeout callback</p> * <p>
* * Enable command timeout callback
* <p>This will invoke the onCommandResult() callback with exitCode WATCHDOG_EXIT if a command takes longer than watchdogTimeout * </p>
* seconds to complete.</p> * <p>
* * This will invoke the onCommandResult() callback with exitCode
* <p>If a watchdog timeout occurs, it generally means that the Interactive session is out of sync with the shell process. The * WATCHDOG_EXIT if a command takes longer than watchdogTimeout seconds
* caller should close the current session and open a new one.</p> * to complete.
* </p>
* <p>
* 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.
* </p>
* *
* @param watchdogTimeout Timeout, in seconds; 0 to disable * @param watchdogTimeout Timeout, in seconds; 0 to disable
* @return This Builder object for method chaining * @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;
}
/** /**
* <p>Enable/disable reduced logcat output</p> * <p>
* * Enable/disable reduced logcat output
* <p>Note that this is a global setting</p> * </p>
* <p>
* Note that this is a global setting
* </p>
* *
* @param useMinimal true for reduced output, false for full output * @param useMinimal true for reduced output, false for full output
* @return This Builder object for method chaining * @return This Builder object for method chaining
...@@ -648,11 +886,13 @@ public class Shell { ...@@ -648,11 +886,13 @@ public class Shell {
/** /**
* Construct a {@link Shell.Interactive} instance, and start the 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 * Construct a {@link Shell.Interactive} instance, try to start the
* call onCommandResultListener to report success or failure * shell, and call onCommandResultListener to report success or failure
* *
* @param onCommandResultListener Callback to return shell open status * @param onCommandResultListener Callback to return shell open status
*/ */
...@@ -662,55 +902,70 @@ public class Shell { ...@@ -662,55 +902,70 @@ public class Shell {
} }
/** /**
* <p>An interactive shell - initially created with {@link Shell.Builder} - that * <p>
* executes blocks of commands you supply in the background, optionally calling * An interactive shell - initially created with {@link Shell.Builder} -
* callbacks as each block completes.</p> * that executes blocks of commands you supply in the background, optionally
* * calling callbacks as each block completes.
* <p>STDERR output can be supplied as well, but due to compatibility with older * </p>
* Android versions, wantSTDERR is not implemented using redirectErrorStream, * <p>
* but rather appended to the output. STDOUT and STDERR are thus not guaranteed to * STDERR output can be supplied as well, but due to compatibility with
* be in the correct order in the output.</p> * older Android versions, wantSTDERR is not implemented using
* * redirectErrorStream, but rather appended to the output. STDOUT and STDERR
* <p>Note as well that the close() and waitForIdle() methods will intentionally * are thus not guaranteed to be in the correct order in the output.
* crash when run in debug mode from the main thread of the application. Any blocking * </p>
* call should be run from a background thread.</p> * <p>
* * Note as well that the close() and waitForIdle() methods will
* <p>When in debug mode, the code will also excessively log the commands passed to * intentionally crash when run in debug mode from the main thread of the
* and the output returned from the shell.</p> * application. Any blocking call should be run from a background thread.
* * </p>
* <p>Though this function uses background threads to gobble STDOUT and STDERR so * <p>
* a deadlock does not occur if the shell produces massive output, the output is * When in debug mode, the code will also excessively log the commands
* still stored in a List&lt;String&gt;, and as such doing something like <em>'ls -lR /'</em> * passed to and the output returned from the shell.
* will probably have you run out of memory when using a * </p>
* {@link Shell.OnCommandResultListener}. A work-around is to not supply this callback, * <p>
* but using (only) {@link Shell.Builder#setOnSTDOUTLineListener(OnLineListener)}. This * Though this function uses background threads to gobble STDOUT and STDERR
* way, an internal buffer will not be created and wasting your memory.</p> * so a deadlock does not occur if the shell produces massive output, the
* * output is still stored in a List&lt;String&gt;, and as such doing
* something like <em>'ls -lR /'</em> 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.
* </p>
* <h3>Callbacks, threads and handlers</h3> * <h3>Callbacks, threads and handlers</h3>
* * <p>
* <p>On which thread the callbacks execute is dependent on your initialization. You can * On which thread the callbacks execute is dependent on your
* supply a custom Handler using {@link Shell.Builder#setHandler(Handler)} if needed. * initialization. You can supply a custom Handler using
* If you do not supply a custom Handler - unless you set {@link Shell.Builder#setAutoHandler(boolean)} * {@link Shell.Builder#setHandler(Handler)} if needed. If you do not supply
* to false - a Handler will be auto-created if the thread used for instantiation * a custom Handler - unless you set
* of the object has a Looper.</p> * {@link Shell.Builder#setAutoHandler(boolean)} to false - a Handler will
* * be auto-created if the thread used for instantiation of the object has a
* <p>If no Handler was supplied and it was also not auto-created, all callbacks will * Looper.
* be called from either the STDOUT or STDERR gobbler threads. These are important * </p>
* threads that should be blocked as little as possible, as blocking them may in rare * <p>
* cases pause the native process or even create a deadlock.</p> * If no Handler was supplied and it was also not auto-created, all
* * callbacks will be called from either the STDOUT or STDERR gobbler
* <p>The main thread must certainly have a Looper, thus if you call {@link Shell.Builder#open()} * threads. These are important threads that should be blocked as little as
* from the main thread, a handler will (by default) be auto-created, and all the callbacks * possible, as blocking them may in rare cases pause the native process or
* will be called on the main thread. While this is often convenient and easy to code with, * even create a deadlock.
* you should be aware that if your callbacks are 'expensive' to execute, this may negatively * </p>
* impact UI performance.</p> * <p>
* * The main thread must certainly have a Looper, thus if you call
* <p>Background threads usually do <em>not</em> have a Looper, so calling {@link Shell.Builder#open()} * {@link Shell.Builder#open()} from the main thread, a handler will (by
* from such a background thread will (by default) result in all the callbacks being executed * default) be auto-created, and all the callbacks will be called on the
* in one of the gobbler threads. You will have to make sure the code you execute in these callbacks * main thread. While this is often convenient and easy to code with, you
* is thread-safe.</p> * should be aware that if your callbacks are 'expensive' to execute, this
* may negatively impact UI performance.
* </p>
* <p>
* Background threads usually do <em>not</em> 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.
* </p>
*/ */
public static class Interactive { public static class Interactive {
private final Handler handler; private final Handler handler;
private final boolean autoHandler; private final boolean autoHandler;
private final String shell; private final String shell;
...@@ -724,7 +979,7 @@ public class Shell { ...@@ -724,7 +979,7 @@ public class Shell {
private Process process = null; private Process process = null;
private DataOutputStream STDIN = null; private DataOutputStream STDIN = null;
private StreamGobbler STDOUT = null; private StreamGobbler STDOUT = null;
private StreamGobbler STDERR = null; private StreamGobbler STDERR = null;
private ScheduledThreadPoolExecutor watchdog = null; private ScheduledThreadPoolExecutor watchdog = null;
private volatile boolean running = false; private volatile boolean running = false;
...@@ -747,7 +1002,8 @@ public class Shell { ...@@ -747,7 +1002,8 @@ public class Shell {
* *
* @param builder Builder class to take values from * @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; autoHandler = builder.autoHandler;
shell = builder.shell; shell = builder.shell;
wantSTDERR = builder.wantSTDERR; wantSTDERR = builder.wantSTDERR;
...@@ -757,7 +1013,8 @@ public class Shell { ...@@ -757,7 +1013,8 @@ public class Shell {
onSTDERRLineListener = builder.onSTDERRLineListener; onSTDERRLineListener = builder.onSTDERRLineListener;
watchdogTimeout = builder.watchdogTimeout; 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(), // to whichever thread created us. Would normally do this in open(),
// but then we could not declare handler as final // but then we could not declare handler as final
if ((Looper.myLooper() != null) && (builder.handler == null) && autoHandler) { if ((Looper.myLooper() != null) && (builder.handler == null) && autoHandler) {
...@@ -770,11 +1027,13 @@ public class Shell { ...@@ -770,11 +1027,13 @@ public class Shell {
if (onCommandResultListener == null) { if (onCommandResultListener == null) {
return; return;
} else if (ret == false) { } else if (ret == false) {
onCommandResultListener.onCommandResult(0, OnCommandResultListener.SHELL_EXEC_FAILED, null); onCommandResultListener.onCommandResult(0,
OnCommandResultListener.SHELL_EXEC_FAILED, null);
return; 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 // timeout for all subsequent operations
watchdogTimeout = 60; watchdogTimeout = 60;
addCommand(Shell.availableTestCommands, 0, new OnCommandResultListener() { addCommand(Shell.availableTestCommands, 0, new OnCommandResultListener() {
...@@ -791,11 +1050,11 @@ public class Shell { ...@@ -791,11 +1050,11 @@ public class Shell {
} }
@Override @Override
protected void finalize() throws Throwable { protected void finalize() throws Throwable {
if (!closed && Debug.getSanityChecksEnabledEffective()) { if (!closed && Debug.getSanityChecksEnabledEffective()) {
// waste of resources // waste of resources
Debug.log(ShellNotClosedException.EXCEPTION_NOT_CLOSED); Debug.log(ShellNotClosedException.EXCEPTION_NOT_CLOSED);
throw new ShellNotClosedException(); throw new ShellNotClosedException();
} }
super.finalize(); super.finalize();
} }
...@@ -805,73 +1064,170 @@ public class Shell { ...@@ -805,73 +1064,170 @@ public class Shell {
* *
* @param command Command to execute * @param command Command to execute
*/ */
public void addCommand(String command) { addCommand(command, 0, null); } public void addCommand(String command) {
addCommand(command, 0, (OnCommandResultListener) null);
}
/** /**
* <p>Add a command to execute, with a callback to be called on completion</p> * <p>
* * Add a command to execute, with a callback to be called on completion
* <p>The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details</p> * </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
* *
* @param command Command to execute * @param command Command to execute
* @param code User-defined value passed back to the callback * @param code User-defined value passed back to the callback
* @param onCommandResultListener Callback to be called on completion * @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);
}
/**
* <p>
* 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.
* </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
*
* @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 * Add commands to execute
* *
* @param commands Commands to execute * @param commands Commands to execute
*/ */
public void addCommand(List<String> commands) { addCommand(commands, 0, null); } public void addCommand(List<String> commands) {
addCommand(commands, 0, (OnCommandResultListener) null);
}
/** /**
* <p>Add commands to execute, with a callback to be called on completion (of all commands)</p> * <p>
* Add commands to execute, with a callback to be called on completion
* (of all commands)
* </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
* *
* <p>The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details</p> * @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<String> commands, int code,
OnCommandResultListener onCommandResultListener) {
addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener);
}
/**
* <p>
* 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.
* </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
* *
* @param commands Commands to execute * @param commands Commands to execute
* @param code User-defined value passed back to the callback * @param code User-defined value passed back to the callback
* @param onCommandResultListener Callback to be called on completion (of all commands) * @param onCommandLineListener Callback
*/ */
public void addCommand(List<String> commands, int code, OnCommandResultListener onCommandResultListener) { addCommand(commands.toArray(new String[commands.size()]), code, onCommandResultListener); } public void addCommand(List<String> commands, int code,
OnCommandLineListener onCommandLineListener) {
addCommand(commands.toArray(new String[commands.size()]), code, onCommandLineListener);
}
/** /**
* Add commands to execute * Add commands to execute
* *
* @param commands 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);
}
/** /**
* <p>Add commands to execute, with a callback to be called on completion (of all commands)</p> * <p>
* Add commands to execute, with a callback to be called on completion
* (of all commands)
* </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
* *
* <p>The thread on which the callback executes is dependent on various factors, see {@link Shell.Interactive} for further details</p> * @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();
}
/**
* <p>
* 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.
* </p>
* <p>
* The thread on which the callback executes is dependent on various
* factors, see {@link Shell.Interactive} for further details
* </p>
* *
* @param commands Commands to execute * @param commands Commands to execute
* @param code User-defined value passed back to the callback * @param code User-defined value passed back to the callback
* @param onCommandResultListener Callback to be called on completion (of all commands) * @param onCommandLineListener Callback
*/ */
public synchronized void addCommand(String[] commands, int code, OnCommandResultListener onCommandResultListener) { public synchronized void addCommand(String[] commands, int code,
this.commands.add(new Command(commands, code, onCommandResultListener)); OnCommandLineListener onCommandLineListener) {
this.commands.add(new Command(commands, code, null, onCommandLineListener));
runNextCommand(); runNextCommand();
} }
/** /**
* Run the next command if any and if ready, signals idle state if no commands left * Run the next command if any and if ready, signals idle state if no
* commands left
*/ */
private void runNextCommand() { private void runNextCommand() {
runNextCommand(true); runNextCommand(true);
} }
/** /**
* Called from a ScheduledThreadPoolExecutor timer thread every second when there is an outstanding command * Called from a ScheduledThreadPoolExecutor timer thread every second
* when there is an outstanding command
*/ */
private synchronized void handleWatchdog() { private synchronized void handleWatchdog() {
final int exitCode; final int exitCode;
if (watchdog == null) return; if (watchdog == null)
if (watchdogTimeout == 0) return; return;
if (watchdogTimeout == 0)
return;
if (!isRunning()) { if (!isRunning()) {
exitCode = OnCommandResultListener.SHELL_DIED; exitCode = OnCommandResultListener.SHELL_DIED;
...@@ -927,13 +1283,14 @@ public class Shell { ...@@ -927,13 +1283,14 @@ public class Shell {
/** /**
* Run the next command if any and if ready * 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) { private void runNextCommand(boolean notifyIdle) {
// must always be called from a synchronized method // must always be called from a synchronized method
boolean running = isRunning(); boolean running = isRunning();
if (!running) idle = true; if (!running)
idle = true;
if (running && idle && (commands.size() > 0)) { if (running && idle && (commands.size() > 0)) {
Command command = commands.get(0); Command command = commands.get(0);
...@@ -942,22 +1299,25 @@ public class Shell { ...@@ -942,22 +1299,25 @@ public class Shell {
buffer = null; buffer = null;
lastExitCode = 0; lastExitCode = 0;
lastMarkerSTDOUT = null; lastMarkerSTDOUT = null;
lastMarkerSTDERR = null; lastMarkerSTDERR = null;
if (command.commands.length > 0) { if (command.commands.length > 0) {
try { try {
if (command.onCommandResultListener != null) { if (command.onCommandResultListener != null) {
// no reason to store the output if we don't have an OnCommandResultListener // no reason to store the output if we don't have an
// user should catch the output with an OnLineListener in this case // OnCommandResultListener
buffer = Collections.synchronizedList(new ArrayList<String>()); // user should catch the output with an
// OnLineListener in this case
buffer = Collections.synchronizedList(new ArrayList<String>());
} }
idle = false; idle = false;
this.command = command; this.command = command;
startWatchdog(); startWatchdog();
for (String write : command.commands) { for (String write : command.commands) {
Debug.logCommand(String.format("[%s+] %s", shell.toUpperCase(Locale.ENGLISH), write)); Debug.logCommand(String.format("[%s+] %s",
STDIN.write((write + "\n").getBytes("UTF-8")); 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 + " $?\n").getBytes("UTF-8"));
STDIN.write(("echo " + command.marker + " >&2\n").getBytes("UTF-8")); STDIN.write(("echo " + command.marker + " >&2\n").getBytes("UTF-8"));
...@@ -966,7 +1326,7 @@ public class Shell { ...@@ -966,7 +1326,7 @@ public class Shell {
} }
} else { } else {
runNextCommand(false); runNextCommand(false);
} }
} else if (!running) { } else if (!running) {
// our shell died for unknown reasons - abort all submissions // our shell died for unknown reasons - abort all submissions
while (commands.size() > 0) { while (commands.size() > 0) {
...@@ -975,7 +1335,7 @@ public class Shell { ...@@ -975,7 +1335,7 @@ public class Shell {
} }
if (idle && notifyIdle) { if (idle && notifyIdle) {
synchronized(idleSync) { synchronized (idleSync) {
idleSync.notifyAll(); idleSync.notifyAll();
} }
} }
...@@ -985,17 +1345,15 @@ public class Shell { ...@@ -985,17 +1345,15 @@ public class Shell {
* Processes a STDOUT/STDERR line containing an end/exitCode marker * Processes a STDOUT/STDERR line containing an end/exitCode marker
*/ */
private synchronized void processMarker() { private synchronized void processMarker() {
if (command.marker.equals(lastMarkerSTDOUT) && (command.marker.equals(lastMarkerSTDERR))) { if (command.marker.equals(lastMarkerSTDOUT)
if (buffer != null) { && (command.marker.equals(lastMarkerSTDERR))) {
postCallback(command, lastExitCode, buffer); postCallback(command, lastExitCode, buffer);
}
stopWatchdog(); stopWatchdog();
command = null; command = null;
buffer = null; buffer = null;
idle = true; idle = true;
runNextCommand(); runNextCommand();
} }
} }
/** /**
...@@ -1011,7 +1369,7 @@ public class Shell { ...@@ -1011,7 +1369,7 @@ public class Shell {
final OnLineListener fListener = listener; final OnLineListener fListener = listener;
startCallback(); startCallback();
handler.post(new Runnable() { handler.post(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
...@@ -1024,7 +1382,7 @@ public class Shell { ...@@ -1024,7 +1382,7 @@ public class Shell {
} else { } else {
listener.onLine(line); listener.onLine(line);
} }
} }
} }
/** /**
...@@ -1033,7 +1391,7 @@ public class Shell { ...@@ -1033,7 +1391,7 @@ public class Shell {
* @param line Line to add * @param line Line to add
*/ */
private synchronized void addBuffer(String line) { private synchronized void addBuffer(String line) {
if (buffer != null) { if (buffer != null) {
buffer.add(line); buffer.add(line);
} }
} }
...@@ -1050,12 +1408,17 @@ public class Shell { ...@@ -1050,12 +1408,17 @@ public class Shell {
/** /**
* Schedule a callback to run on the appropriate thread * Schedule a callback to run on the appropriate thread
*/ */
private void postCallback(final Command fCommand, final int fExitCode, final List<String> fOutput) { private void postCallback(final Command fCommand, final int fExitCode,
if (fCommand.onCommandResultListener == null) { final List<String> fOutput) {
if (fCommand.onCommandResultListener == null && fCommand.onCommandLineListener == null) {
return; return;
} }
if (handler == null) { 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; return;
} }
startCallback(); startCallback();
...@@ -1063,7 +1426,12 @@ public class Shell { ...@@ -1063,7 +1426,12 @@ public class Shell {
@Override @Override
public void run() { public void run() {
try { 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 { } finally {
endCallback(); endCallback();
} }
...@@ -1072,7 +1440,8 @@ public class Shell { ...@@ -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() { private void endCallback() {
synchronized (callbackSync) { synchronized (callbackSync) {
...@@ -1084,8 +1453,8 @@ public class Shell { ...@@ -1084,8 +1453,8 @@ public class Shell {
} }
/** /**
* Internal call that launches the shell, starts gobbling, and starts executing commands. * Internal call that launches the shell, starts gobbling, and starts
* See {@link Shell.Interactive} * executing commands. See {@link Shell.Interactive}
* *
* @return Opened successfully ? * @return Opened successfully ?
*/ */
...@@ -1093,7 +1462,8 @@ public class Shell { ...@@ -1093,7 +1462,8 @@ public class Shell {
Debug.log(String.format("[%s%%] START", shell.toUpperCase(Locale.ENGLISH))); Debug.log(String.format("[%s%%] START", shell.toUpperCase(Locale.ENGLISH)));
try { 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) { if (environment.size() == 0) {
process = Runtime.getRuntime().exec(shell); process = Runtime.getRuntime().exec(shell);
} else { } else {
...@@ -1104,50 +1474,55 @@ public class Shell { ...@@ -1104,50 +1474,55 @@ public class Shell {
String[] env = new String[newEnvironment.size()]; String[] env = new String[newEnvironment.size()];
for (Map.Entry<String, String> entry : newEnvironment.entrySet()) { for (Map.Entry<String, String> entry : newEnvironment.entrySet()) {
env[i] = entry.getKey() + "=" + entry.getValue(); env[i] = entry.getKey() + "=" + entry.getValue();
i++; i++;
} }
process = Runtime.getRuntime().exec(shell, env); process = Runtime.getRuntime().exec(shell, env);
} }
STDIN = new DataOutputStream(process.getOutputStream()); STDIN = new DataOutputStream(process.getOutputStream());
STDOUT = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "-", process.getInputStream(), new OnLineListener() { STDOUT = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "-",
@Override process.getInputStream(), new OnLineListener() {
public void onLine(String line) { @Override
synchronized (Interactive.this) { public void onLine(String line) {
if (command == null) { synchronized (Interactive.this) {
return; if (command == null) {
} return;
if (line.startsWith(command.marker)) { }
try { if (line.startsWith(command.marker)) {
lastExitCode = Integer.valueOf(line.substring(command.marker.length() + 1), 10); try {
} catch (Exception e) { 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;
} }
if (line.startsWith(command.marker)) { });
lastMarkerSTDERR = command.marker; STDERR = new StreamGobbler(shell.toUpperCase(Locale.ENGLISH) + "*",
processMarker(); process.getErrorStream(), new OnLineListener() {
} else { @Override
if (wantSTDERR) addBuffer(line); public void onLine(String line) {
processLine(line, onSTDERRLineListener); 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 // start gobbling and write our commands to the shell
STDOUT.start(); STDOUT.start();
...@@ -1166,43 +1541,49 @@ public class Shell { ...@@ -1166,43 +1541,49 @@ public class Shell {
} }
/** /**
* Close shell and clean up all resources. Call this when you are done with the shell. * Close shell and clean up all resources. Call this when you are done
* If the shell is not idle (all commands completed) you should not call this method * with the shell. If the shell is not idle (all commands completed) you
* from the main UI thread because it may block for a long time. This method will * should not call this method from the main UI thread because it may
* intentionally crash your app (if in debug mode) if you try to do this anyway. * 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() { public void close() {
boolean _idle = isIdle(); // idle must be checked synchronized boolean _idle = isIdle(); // idle must be checked synchronized
synchronized (this) { synchronized (this) {
if (!running) return; if (!running)
return;
running = false; running = false;
closed = true; closed = true;
} }
// This method should not be called from the main thread unless the shell is idle // This method should not be called from the main thread unless the
// and can be cleaned up with (minimal) waiting. Only throw in debug mode. // shell is idle and can be cleaned up with (minimal) waiting. Only
// throw in debug mode.
if (!_idle && Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) { if (!_idle && Debug.getSanityChecksEnabledEffective() && Debug.onMainThread()) {
Debug.log(ShellOnMainThreadException.EXCEPTION_NOT_IDLE); Debug.log(ShellOnMainThreadException.EXCEPTION_NOT_IDLE);
throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_NOT_IDLE); throw new ShellOnMainThreadException(ShellOnMainThreadException.EXCEPTION_NOT_IDLE);
} }
if (!_idle) waitForIdle(); if (!_idle)
waitForIdle();
try { try {
STDIN.write(("exit\n").getBytes("UTF-8")); STDIN.write(("exit\n").getBytes("UTF-8"));
STDIN.flush(); 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(); process.waitFor();
// make sure our threads are done gobbling, our streams are closed, and the process is // make sure our threads are done gobbling, our streams are
// destroyed - while the latter two shouldn't be needed in theory, and may even produce // closed, and the process is destroyed - while the latter two
// warnings, in "normal" Java they are required for guaranteed cleanup of resources, so // shouldn't be needed in theory, and may even produce warnings,
// lets be safe and do this on Android as well // in "normal" Java they are required for guaranteed cleanup of
try { // resources, so lets be safe and do this on Android as well
try {
STDIN.close(); STDIN.close();
} catch (IOException e) { } catch (IOException e) {
} }
STDOUT.join(); STDOUT.join();
STDERR.join(); STDERR.join();
...@@ -1218,21 +1599,21 @@ public class Shell { ...@@ -1218,21 +1599,21 @@ public class Shell {
} }
/** /**
* Try to clean up as much as possible from a shell that's gotten itself wedged. * Try to clean up as much as possible from a shell that's gotten itself
* Hopefully the StreamGobblers will croak on their own when the other side of * wedged. Hopefully the StreamGobblers will croak on their own when the
* the pipe is closed. * other side of the pipe is closed.
*/ */
public synchronized void kill() { public synchronized void kill() {
running = false; running = false;
closed = true; closed = true;
try { try {
STDIN.close(); STDIN.close();
} catch (IOException e) { } catch (IOException e) {
} }
try { try {
process.destroy(); process.destroy();
} catch (Exception e) { } catch (Exception e) {
} }
} }
...@@ -1242,12 +1623,15 @@ public class Shell { ...@@ -1242,12 +1623,15 @@ public class Shell {
* @return Shell running ? * @return Shell running ?
*/ */
public boolean isRunning() { public boolean isRunning() {
if (process == null) {
return false;
}
try { try {
// if this throws, we're still running // if this throws, we're still running
process.exitValue(); process.exitValue();
return false; return false;
} catch (IllegalThreadStateException e) { } catch (IllegalThreadStateException e) {
} }
return true; return true;
} }
...@@ -1259,31 +1643,42 @@ public class Shell { ...@@ -1259,31 +1643,42 @@ public class Shell {
public synchronized boolean isIdle() { public synchronized boolean isIdle() {
if (!isRunning()) { if (!isRunning()) {
idle = true; idle = true;
synchronized(idleSync) { synchronized (idleSync) {
idleSync.notifyAll(); idleSync.notifyAll();
} }
} }
return idle; return idle;
} }
/** /**
* <p>Wait for idle state. As this is a blocking call, you should not call it from the main UI thread. * <p>
* If you do so and debug mode is enabled, this method will intentionally crash your app.</p> * 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,
* <p>If not interrupted, this method will not return until all commands have finished executing. * this method will intentionally crash your app.
* Note that this does not necessarily mean that all the callbacks have fired yet.</p> * </p>
* * <p>
* <p>If no Handler is used, all callbacks will have been executed when this method returns. If * If not interrupted, this method will not return until all commands
* a Handler is used, and this method is called from a different thread than associated with the * have finished executing. Note that this does not necessarily mean
* Handler's Looper, all callbacks will have been executed when this method returns as well. * that all the callbacks have fired yet.
* If however a Handler is used but this method is called from the same thread as associated * </p>
* with the Handler's Looper, there is no way to know.</p> * <p>
* * If no Handler is used, all callbacks will have been executed when
* <p>In practice this means that in most simple cases all callbacks will have completed when this * this method returns. If a Handler is used, and this method is called
* method returns, but if you actually depend on this behavior, you should make certain this is * from a different thread than associated with the Handler's Looper,
* indeed the case.</p> * 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
* <p>See {@link Shell.Interactive} for further details on threading and handlers</p> * same thread as associated with the Handler's Looper, there is no way
* to know.
* </p>
* <p>
* 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.
* </p>
* <p>
* See {@link Shell.Interactive} for further details on threading and
* handlers
* </p>
* *
* @return True if wait complete, false if wait interrupted * @return True if wait complete, false if wait interrupted
*/ */
...@@ -1297,28 +1692,27 @@ public class Shell { ...@@ -1297,28 +1692,27 @@ public class Shell {
synchronized (idleSync) { synchronized (idleSync) {
while (!idle) { while (!idle) {
try { try {
idleSync.wait(); idleSync.wait();
} catch (InterruptedException e) { } catch (InterruptedException e) {
return false; return false;
} }
} }
} }
if ( if ((handler != null) &&
(handler != null) && (handler.getLooper() != null) &&
(handler.getLooper() != null) && (handler.getLooper() != Looper.myLooper())) {
(handler.getLooper() != Looper.myLooper()) // If the callbacks are posted to a different thread than
) { // this one, we can wait until all callbacks have called
// If the callbacks are posted to a different thread than this one, we can wait until // before returning. If we don't use a Handler at all, the
// all callbacks have called before returning. If we don't use a Handler at all, // callbacks are already called before we get here. If we do
// the callbacks are already called before we get here. If we do use a Handler but // use a Handler but we use the same Looper, waiting here
// we use the same Looper, waiting here would actually block the callbacks from being // would actually block the callbacks from being called
// called
synchronized (callbackSync) { synchronized (callbackSync) {
while (callbacks > 0) { while (callbacks > 0) {
try { try {
callbackSync.wait(); callbackSync.wait();
} catch (InterruptedException e) { } catch (InterruptedException e) {
return false; return false;
} }
...@@ -1336,7 +1730,7 @@ public class Shell { ...@@ -1336,7 +1730,7 @@ public class Shell {
* @return Handler used ? * @return Handler used ?
*/ */
public boolean hasHandler() { public boolean hasHandler() {
return (handler != null); return (handler != null);
} }
} }
} }
/* /*
* Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
......
/* /*
* Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
......
/* /*
* Copyright (C) 2012-2013 Jorrit "Chainfire" Jongma * Copyright (C) 2012-2014 Jorrit "Chainfire" Jongma
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
......
...@@ -341,6 +341,7 @@ class Shadowsocks ...@@ -341,6 +341,7 @@ class Shadowsocks
ab.clear() ab.clear()
ab.append("kill -9 `cat /data/data/com.github.shadowsocks/redsocks.pid`") 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.conf")
ab.append("rm /data/data/com.github.shadowsocks/redsocks.pid")
ab.append(Utils.getIptables + " -t nat -F OUTPUT") ab.append(Utils.getIptables + " -t nat -F OUTPUT")
Console.runRootCommand(ab.toArray) Console.runRootCommand(ab.toArray)
......
...@@ -137,18 +137,27 @@ class ShadowsocksNatService extends Service with BaseService { ...@@ -137,18 +137,27 @@ class ShadowsocksNatService extends Service with BaseService {
if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" ")) if (BuildConfig.DEBUG) Log.d(TAG, cmd.mkString(" "))
Core.sstunnel(cmd.toArray) Core.sstunnel(cmd.toArray)
} else { } else {
val conf = { val cmdBuf = new ArrayBuffer[String]
ConfigUtils.PDNSD.format("127.0.0.1") 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 => { ConfigUtils.printToFile(new File(Path.BASE + "pdnsd.conf"))(p => {
p.println(conf) p.println(conf)
}) })
val cmd = Path.BASE + "pdnsd -c " + Path.BASE + "pdnsd.conf"
val args = "pdnsd -c " + Path.BASE + "pdnsd.conf"
val cmd = Path.BASE + args
if (BuildConfig.DEBUG) Log.d(TAG, cmd) if (BuildConfig.DEBUG) Log.d(TAG, cmd)
Console.runRootCommand(cmd) Core.pdnsd(cmd.split(" "))
} }
} }
...@@ -172,7 +181,7 @@ class ShadowsocksNatService extends Service with BaseService { ...@@ -172,7 +181,7 @@ class ShadowsocksNatService extends Service with BaseService {
p.println(conf) p.println(conf)
}) })
val cmd = Path.BASE + args val cmd = Path.BASE + args
Console.runRootCommand(cmd) Console.runCommand(cmd)
} }
/** Called when the activity is first created. */ /** Called when the activity is first created. */
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment