From 2b56dd5d8f5b03cbb6c8cfb7caf706dedc29259c Mon Sep 17 00:00:00 2001 From: Samuel Holland Date: Mon, 31 Jul 2017 19:00:05 -0500 Subject: [PATCH] RootShell: Add helper class for running commands as root Signed-off-by: Jason A. Donenfeld --- .../java/com/wireguard/android/RootShell.java | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) create mode 100644 app/src/main/java/com/wireguard/android/RootShell.java 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 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; + } +}