/** * <p> * Runs commands using the supplied shell, and returns the output, or null * in case of errors. * </p> * <p> * 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. * </p> * <p> * 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. * </p> * <p> * When in debug mode, the code will also excessively log the commands * passed to and the output returned from the shell. * </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<String>, 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 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<string> run(string shell, string[] commands, string[] environment, bool wantSTDERR) { string shellUpper = shell.ToUpperInvariant (); //.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); // vhe throw new ShellOnMainThreadException (ShellOnMainThreadException.EXCEPTION_COMMAND); } Debug.logCommand (string.Format ("[{0}] START", shellUpper)); var res = Java.Util.Collections.SynchronizedList (new System.Collections.ArrayList ()); try { // Combine passed environment with system environment if (environment != null) { var newEnvironment = new Dictionary<string, string> (global::Java.Lang.JavaSystem.Getenv ()); //int split; var split = new char[]{ '=' }; foreach (string entry in environment) { var splat = entry.Split (split, 1, StringSplitOptions.RemoveEmptyEntries); if (splat != null && splat.Length == 2) { newEnvironment [splat [0]] = splat [1]; } } // int i = 0; // environment = new String[newEnvironment.size()]; // foreach (Map.Entry<String, String> entry in newEnvironment.entrySet()) { // environment[i] = entry.getKey() + "=" + entry.getValue(); // i++; // } environment = newEnvironment.Select (x => string.Format ("{0}={1}", x.Key, x.Value)).ToArray (); } // setup our process, retrieve STDIN stream, and STDOUT/STDERR // gobblers Process process = Runtime.GetRuntime ().Exec (shell, environment); var STDIN = new Java.IO.DataOutputStream (process.OutputStream); StreamGobbler STDOUT = new StreamGobbler (shellUpper + "-", process.InputStream, res); StreamGobbler STDERR = new StreamGobbler (shellUpper + "*", process.ErrorStream, wantSTDERR ? res : null); // start gobbling and write our commands to the shell STDOUT.Start (); STDERR.Start (); try { foreach (string write in commands) { Debug.logCommand (string.Format ("[{0}+] {1}", shellUpper, write)); STDIN.Write ((write + "\n").getBytes ("UTF-8")); STDIN.Flush (); } STDIN.Write ("exit\n".getBytes ("UTF-8")); STDIN.Flush (); } catch (System.Exception e) { if (e.ToString ().Contains ("EPIPE")) { // method most horrid to catch broken pipe, in which case we // do nothing. the command is not a shell, the shell closed // STDIN, the script already contained the exit command, etc. // these cases we want the output instead of returning null } else { // other issues we don't know how to handle, leads to // returning null throw; } } // 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 (System.Exception e) { // might be closed already } STDOUT.Join (); STDERR.Join (); process.Destroy (); // in case of su, 255 usually indicates access denied if (SU.isSU (shell) && (process.ExitValue() != 255)) { // vhe res = null; } } catch (Java.IO.IOException e) { // shell probably not found res = null; } catch (InterruptedException e) { // this should really be re-thrown res = null; } Debug.logCommand (string.Format ("[{0}%] END", shell.ToUpperInvariant ())); var result = new List<string> (); foreach (var r in res) result.Add (r.ToString ()); return result; }
/** * <p> * Runs commands using the supplied shell, and returns the output, or null * in case of errors. * </p> * <p> * 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. * </p> * <p> * 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. * </p> * <p> * When in debug mode, the code will also excessively log the commands * passed to and the output returned from the shell. * </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<String>, 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 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 <string> run(string shell, string[] commands, string[] environment, bool wantSTDERR) { string shellUpper = shell.ToUpperInvariant(); //.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("[{0}] START", shellUpper)); var res = Java.Util.Collections.SynchronizedList(new System.Collections.ArrayList()); try { // Combine passed environment with system environment if (environment != null) { var newEnvironment = new Dictionary <string, string> (global::Java.Lang.JavaSystem.Getenv()); //int split; var split = new char[] { '=' }; foreach (string entry in environment) { var splat = entry.Split(split, 1, StringSplitOptions.RemoveEmptyEntries); if (splat != null && splat.Length == 2) { newEnvironment [splat [0]] = splat [1]; } } int i = 0; // environment = new String[newEnvironment.size()]; // foreach (Map.Entry<String, String> entry in newEnvironment.entrySet()) { // environment[i] = entry.getKey() + "=" + entry.getValue(); // i++; // } environment = newEnvironment.Select(x => string.Format("{0}={1}", x.Key, x.Value)).ToArray(); } // setup our process, retrieve STDIN stream, and STDOUT/STDERR // gobblers Process process = Runtime.GetRuntime().Exec(shell, environment); var STDIN = new Java.IO.DataOutputStream(process.OutputStream); StreamGobbler STDOUT = new StreamGobbler(shellUpper + "-", process.InputStream, res); StreamGobbler STDERR = new StreamGobbler(shellUpper + "*", process.ErrorStream, wantSTDERR ? res : null); // start gobbling and write our commands to the shell STDOUT.Start(); STDERR.Start(); try { foreach (string write in commands) { Debug.logCommand(string.Format("[{0}+] {1}", shellUpper, write)); STDIN.Write((write + "\n").getBytes("UTF-8")); STDIN.Flush(); } STDIN.Write("exit\n".getBytes("UTF-8")); STDIN.Flush(); } catch (System.Exception e) { if (e.ToString().Contains("EPIPE")) { // method most horrid to catch broken pipe, in which case we // do nothing. the command is not a shell, the shell closed // STDIN, the script already contained the exit command, etc. // these cases we want the output instead of returning null } else { // other issues we don't know how to handle, leads to // returning null throw; } } // 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 (System.Exception e) { // might be closed already } 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 (Java.IO.IOException e) { // shell probably not found res = null; } catch (InterruptedException e) { // this should really be re-thrown res = null; } Debug.logCommand(string.Format("[{0}%] END", shell.ToUpperInvariant())); var result = new List <string> (); foreach (var r in res) { result.Add(r.ToString()); } return(result); }