aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/app/src/main/java/com/wireguard/android/backends/RootShell.java
diff options
context:
space:
mode:
authorSamuel Holland <samuel@sholland.org>2017-11-24 21:13:55 -0600
committerSamuel Holland <samuel@sholland.org>2017-11-24 21:16:37 -0600
commit50a7a12de279574dd15f4db5cf5ea7aa984b7c80 (patch)
treedd1d5cad145048cb912195902e2a5fd364964a17 /app/src/main/java/com/wireguard/android/backends/RootShell.java
parentcli: move to android_kernel_wireguard (diff)
downloadwireguard-android-50a7a12de279574dd15f4db5cf5ea7aa984b7c80.tar.xz
wireguard-android-50a7a12de279574dd15f4db5cf5ea7aa984b7c80.zip
VpnService: Move it to a backends package
It should be split into two pieces: configuration file management (loading/saving/renaming/deleting) and calling into wg-quick via RootShell. The configuration file management part should then go back into the main package. This is in preparation for adding additional backends based on wg(8) and wireguard-go. Signed-off-by: Jason A. Donenfeld <Jason@zx2c4.com>
Diffstat (limited to 'app/src/main/java/com/wireguard/android/backends/RootShell.java')
-rw-r--r--app/src/main/java/com/wireguard/android/backends/RootShell.java86
1 files changed, 86 insertions, 0 deletions
diff --git a/app/src/main/java/com/wireguard/android/backends/RootShell.java b/app/src/main/java/com/wireguard/android/backends/RootShell.java
new file mode 100644
index 00000000..0b529065
--- /dev/null
+++ b/app/src/main/java/com/wireguard/android/backends/RootShell.java
@@ -0,0 +1,86 @@
+package com.wireguard.android.backends;
+
+import android.content.Context;
+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_TEMPLATE = "export TMPDIR=%s\ntrap 'echo $?' EXIT\n";
+ private static final String TAG = "RootShell";
+
+ private final byte[] setupCommands;
+ private final String shell;
+
+ RootShell(final Context context) {
+ this(context, "su");
+ }
+
+ RootShell(final Context context, final String shell) {
+ final String tmpdir = context.getCacheDir().getPath();
+ setupCommands = String.format(SETUP_TEMPLATE, tmpdir).getBytes(StandardCharsets.UTF_8);
+ this.shell = shell;
+ }
+
+ /**
+ * 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.
+ */
+ int run(final List<String> output, final 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(shell).start();
+ final OutputStream stdin = process.getOutputStream();
+ stdin.write(setupCommands);
+ for (final 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;
+ }
+}