2017-08-01 02:00:05 +02:00
|
|
|
package com.wireguard.android;
|
|
|
|
|
2017-08-01 06:21:59 +02:00
|
|
|
import android.content.Context;
|
2017-08-01 02:00:05 +02:00
|
|
|
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.
|
|
|
|
*/
|
2017-08-04 07:37:29 +02:00
|
|
|
private static final String SETUP_TEMPLATE = "export TMPDIR=%s\ntrap 'echo $?' EXIT\n";
|
2017-08-01 02:00:05 +02:00
|
|
|
private static final String TAG = "RootShell";
|
|
|
|
|
2017-08-13 14:24:03 +02:00
|
|
|
private final byte[] setupCommands;
|
2017-08-04 07:37:58 +02:00
|
|
|
private final String shell;
|
2017-08-01 06:21:59 +02:00
|
|
|
|
2017-08-13 14:24:03 +02:00
|
|
|
RootShell(final Context context) {
|
2017-08-04 07:37:58 +02:00
|
|
|
this(context, "su");
|
|
|
|
}
|
|
|
|
|
2017-08-13 14:24:03 +02:00
|
|
|
RootShell(final Context context, final String shell) {
|
2017-08-01 06:21:59 +02:00
|
|
|
final String tmpdir = context.getCacheDir().getPath();
|
2017-08-04 07:37:29 +02:00
|
|
|
setupCommands = String.format(SETUP_TEMPLATE, tmpdir).getBytes(StandardCharsets.UTF_8);
|
2017-08-04 07:37:58 +02:00
|
|
|
this.shell = shell;
|
2017-08-01 06:21:59 +02:00
|
|
|
}
|
|
|
|
|
2017-08-01 02:00:05 +02:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2017-08-13 14:24:03 +02:00
|
|
|
int run(final List<String> output, final String... commands) {
|
2017-08-01 02:00:05 +02:00
|
|
|
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);
|
2017-08-04 07:37:58 +02:00
|
|
|
final Process process = builder.command(shell).start();
|
2017-08-01 02:00:05 +02:00
|
|
|
final OutputStream stdin = process.getOutputStream();
|
2017-08-01 06:21:59 +02:00
|
|
|
stdin.write(setupCommands);
|
2017-08-13 14:24:03 +02:00
|
|
|
for (final String command : commands)
|
2017-08-01 02:00:05 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|