Magisk/app/src/main/java/com/topjohnwu/magisk/utils/Shell.java

218 lines
6.9 KiB
Java
Raw Normal View History

package com.topjohnwu.magisk.utils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Modified by topjohnwu, based on Chainfire's libsuperuser
*/
public class Shell {
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
2016-08-27 11:02:41 +00:00
public static int rootStatus;
2017-02-05 14:02:14 +00:00
private static boolean isInit = false;
private static Process rootShell;
private static DataOutputStream rootSTDIN;
private static StreamGobbler rootSTDOUT;
2017-02-05 14:02:14 +00:00
private static List<String> rootOutList = Collections.synchronizedList(new ArrayList<String>());
2017-02-05 14:02:14 +00:00
public static void init() {
2016-08-25 10:08:07 +00:00
2017-02-05 14:02:14 +00:00
isInit = true;
try {
rootShell = Runtime.getRuntime().exec("su");
2016-08-27 11:02:41 +00:00
rootStatus = 1;
} catch (IOException err) {
// No root
rootStatus = 0;
return;
}
2016-08-27 11:02:41 +00:00
rootSTDIN = new DataOutputStream(rootShell.getOutputStream());
2016-09-30 02:41:40 +00:00
rootSTDOUT = new StreamGobbler(rootShell.getInputStream(), rootOutList, true);
2016-08-27 11:02:41 +00:00
rootSTDOUT.start();
2016-11-08 21:17:50 +00:00
2016-11-29 05:24:48 +00:00
// Setup umask and PATH
2016-11-08 21:17:50 +00:00
su("umask 022");
2017-02-04 20:40:52 +00:00
su("PATH=`[ -e /dev/busybox ] && echo /dev/busybox || echo /data/busybox`:$PATH");
2016-08-27 11:02:41 +00:00
2016-08-28 22:35:07 +00:00
List<String> ret = su("echo -BOC-", "id");
2016-10-05 16:36:50 +00:00
if (ret == null) {
2016-08-25 10:08:07 +00:00
// Something wrong with root, not allowed?
rootStatus = -1;
return;
}
for (String line : ret) {
if (line.contains("uid=")) {
// id command is working, let's see if we are actually root
rootStatus = line.contains("uid=0") ? rootStatus : -1;
return;
} else if (!line.contains("-BOC-")) {
rootStatus = -1;
return;
}
}
}
2016-08-25 10:08:07 +00:00
public static boolean rootAccess() {
2017-02-05 14:02:14 +00:00
return isInit && rootStatus > 0;
2016-08-25 10:08:07 +00:00
}
public static List<String> sh(String... commands) {
List<String> res = Collections.synchronizedList(new ArrayList<String>());
try {
Process process = Runtime.getRuntime().exec("sh");
DataOutputStream STDIN = new DataOutputStream(process.getOutputStream());
StreamGobbler STDOUT = new StreamGobbler(process.getInputStream(), res);
STDOUT.start();
try {
for (String write : commands) {
STDIN.write((write + "\n").getBytes("UTF-8"));
STDIN.flush();
2016-09-30 02:41:40 +00:00
Logger.shell(false, write);
}
STDIN.write("exit\n".getBytes("UTF-8"));
STDIN.flush();
} catch (IOException e) {
if (!e.getMessage().contains("EPIPE")) {
throw e;
}
}
process.waitFor();
try {
STDIN.close();
} catch (IOException e) {
// might be closed already
}
STDOUT.join();
process.destroy();
} catch (IOException | InterruptedException e) {
// shell probably not found
res = null;
}
return res;
}
// Run with the same shell by default
public static List<String> su(String... commands) {
return su(false, commands);
}
public static List<String> su(boolean newShell, String... commands) {
List<String> res;
Process process;
DataOutputStream STDIN;
StreamGobbler STDOUT;
2017-02-05 14:02:14 +00:00
// Create the default shell if not init
if (!newShell && !isInit)
init();
if (!newShell && !rootAccess())
2016-10-05 16:36:50 +00:00
return null;
if (newShell) {
res = Collections.synchronizedList(new ArrayList<String>());
try {
2016-10-05 16:36:50 +00:00
process = Runtime.getRuntime().exec("su");
STDIN = new DataOutputStream(process.getOutputStream());
STDOUT = new StreamGobbler(process.getInputStream(), res);
2017-02-06 22:02:06 +00:00
// Run the new shell with busybox and proper umask
STDIN.write(("umask 022\n").getBytes("UTF-8"));
STDIN.flush();
STDIN.write(("PATH=`[ -e /dev/busybox ] && echo /dev/busybox || " +
"echo /data/busybox`:$PATH\n").getBytes("UTF-8"));
STDIN.flush();
2016-10-05 16:36:50 +00:00
} catch (IOException err) {
return null;
}
STDOUT.start();
} else {
process = rootShell;
STDIN = rootSTDIN;
STDOUT = rootSTDOUT;
res = rootOutList;
res.clear();
}
try {
2016-08-27 11:02:41 +00:00
for (String write : commands) {
STDIN.write((write + "\n").getBytes("UTF-8"));
STDIN.flush();
2016-09-30 02:41:40 +00:00
Logger.shell(true, write);
2016-08-27 11:02:41 +00:00
}
if (newShell) {
STDIN.write("exit\n".getBytes("UTF-8"));
STDIN.flush();
process.waitFor();
try {
STDIN.close();
} catch (IOException ignore) {
// might be closed already
}
STDOUT.join();
process.destroy();
} else {
STDIN.write(("echo\n").getBytes("UTF-8"));
STDIN.flush();
STDIN.write(("echo \'-root-done-\'\n").getBytes("UTF-8"));
STDIN.flush();
while (true) {
try {
// Process terminated, it means the interactive shell has some issues
process.exitValue();
2017-01-26 05:46:54 +00:00
rootStatus = -1;
return null;
} catch (IllegalThreadStateException e) {
// Process still running, gobble output until done
int end = res.size() - 1;
if (end > 0) {
if (res.get(end).equals("-root-done-")) {
res.remove(end);
if (res.get(end -1).isEmpty()) {
res.remove(end -1);
}
break;
}
}
2017-01-26 05:46:54 +00:00
try { STDOUT.join(100); } catch (InterruptedException err) {
rootStatus = -1;
return null;
}
}
}
}
} catch (IOException e) {
if (!e.getMessage().contains("EPIPE")) {
2016-09-29 19:18:08 +00:00
Logger.dev("Shell: Root shell error...");
2017-01-26 05:46:54 +00:00
rootStatus = -1;
return null;
}
} catch(InterruptedException e) {
2016-09-29 19:18:08 +00:00
Logger.dev("Shell: Root shell error...");
2017-01-26 05:46:54 +00:00
rootStatus = -1;
return null;
}
return new ArrayList<>(res);
}
}