summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorSamuel Holland <samuel@sholland.org>2017-07-31 19:00:05 -0500
committerSamuel Holland <samuel@sholland.org>2017-07-31 19:00:05 -0500
commit2b56dd5d8f5b03cbb6c8cfb7caf706dedc29259c (patch)
tree05d46c9a239731a566d1c908ccf2bd93ceaea170
parent773190f57d707264586f8e19fbe22ef284dd4e1e (diff)
RootShell: Add helper class for running commands as root
Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
-rw-r--r--app/src/main/java/com/wireguard/android/RootShell.java72
1 files changed, 72 insertions, 0 deletions
diff --git a/app/src/main/java/com/wireguard/android/RootShell.java b/app/src/main/java/com/wireguard/android/RootShell.java
new file mode 100644
index 00000000..b80a1b41
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/RootShell.java
@@ -0,0 +1,72 @@
+package com.wireguard.android;
+
+import android.util.Log;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+/**
+ * Helper class for running commands as root.
+ */
+
+class RootShell {
+ /**
+ * Setup commands that are run at the beginning of each root shell. The trap command ensures
+ * access to the return value of the last command, since su itself always exits with 0.
+ */
+ private static final String SETUP = "export TMPDIR=/data/local/tmp\ntrap 'echo $?' EXIT\n";
+ private static final String TAG = "RootShell";
+
+ /**
+ * Run a series of commands in a root shell. These commands are all sent to the same shell
+ * process, so they can be considered a shell script.
+ *
+ * @param output Lines read from stdout and stderr are appended to this list. Pass null if the
+ * output from the shell is not important.
+ * @param commands One or more commands to run as root (each element is a separate line).
+ * @return The exit value of the last command run, or -1 if there was an internal error.
+ */
+ static int run(List<String> output, String... commands) {
+ if (commands.length < 1)
+ throw new IndexOutOfBoundsException("At least one command must be supplied");
+ int exitValue = -1;
+ try {
+ final ProcessBuilder builder = new ProcessBuilder().redirectErrorStream(true);
+ final Process process = builder.command("su").start();
+ final OutputStream stdin = process.getOutputStream();
+ stdin.write(SETUP.getBytes(StandardCharsets.UTF_8));
+ for (String command : commands)
+ stdin.write(command.concat("\n").getBytes(StandardCharsets.UTF_8));
+ stdin.close();
+ Log.d(TAG, "Sent " + commands.length + " command(s), now reading output");
+ final InputStream stdout = process.getInputStream();
+ final BufferedReader stdoutReader =
+ new BufferedReader(new InputStreamReader(stdout, StandardCharsets.UTF_8));
+ String line;
+ String lastLine = null;
+ while ((line = stdoutReader.readLine()) != null) {
+ Log.v(TAG, line);
+ lastLine = line;
+ if (output != null)
+ output.add(line);
+ }
+ process.waitFor();
+ process.destroy();
+ if (lastLine != null) {
+ // Remove the exit value line from the output
+ if (output != null)
+ output.remove(output.size() - 1);
+ exitValue = Integer.parseInt(lastLine);
+ }
+ Log.d(TAG, "Session completed with exit value " + exitValue);
+ } catch (IOException | InterruptedException | NumberFormatException e) {
+ Log.w(TAG, "Session failed with exception", e);
+ }
+ return exitValue;
+ }
+}