Refactor shell (again)

This commit is contained in:
topjohnwu
2017-10-15 23:02:44 +08:00
parent 13bf1b27b4
commit 6f457c0c59
20 changed files with 198 additions and 254 deletions
@@ -6,11 +6,11 @@ import android.text.TextUtils;
import com.topjohnwu.magisk.MagiskManager;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -21,116 +21,103 @@ import java.util.List;
public class Shell {
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
public static int rootStatus;
private static WeakReference<MagiskManager> weakMm;
private final Process shellProcess;
private final DataOutputStream STDIN;
private final DataInputStream STDOUT;
// -1 = no shell; 0 = non root shell; 1 = root shell
public static int status;
private boolean isValid;
private final Process process;
private final OutputStream STDIN;
private final InputStream STDOUT;
private void testRootShell(DataOutputStream in, DataInputStream out) throws IOException {
in.write(("id\n").getBytes("UTF-8"));
in.flush();
String s = new BufferedReader(new InputStreamReader(out)).readLine();
private static void testRootShell(Shell shell) throws IOException {
shell.STDIN.write(("id\n").getBytes("UTF-8"));
shell.STDIN.flush();
String s = new BufferedReader(new InputStreamReader(shell.STDOUT)).readLine();
if (TextUtils.isEmpty(s) || !s.contains("uid=0")) {
in.close();
out.close();
shell.STDIN.close();
shell.STDIN.close();
throw new IOException();
}
}
private Shell() {
rootStatus = 1;
Process process = null;
DataOutputStream in = null;
DataInputStream out = null;
try {
// Try getting global namespace
process = Runtime.getRuntime().exec("su --mount-master");
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
testRootShell(in, out);
} catch (IOException e) {
// Feature not implemented, normal root shell
try {
process = Runtime.getRuntime().exec("su");
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
testRootShell(in, out);
} catch (IOException e1) {
rootStatus = 0;
}
}
if (!rootAccess()) {
// Try to gain non-root sh
try {
process = Runtime.getRuntime().exec("sh");
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
} catch (IOException e) {
// Nothing works....
shellProcess = null;
STDIN = null;
STDOUT = null;
isValid = false;
return;
}
}
isValid = true;
shellProcess = process;
STDIN = in;
STDOUT = out;
sh_raw("umask 022");
public Shell(String command) throws IOException {
process = Runtime.getRuntime().exec(command);
STDIN = process.getOutputStream();
STDOUT = process.getInputStream();
}
private Shell(String command) {
Process process;
DataOutputStream in;
DataInputStream out;
try {
process = Runtime.getRuntime().exec(command);
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
} catch (IOException e) {
// Nothing works....
shellProcess = null;
STDIN = null;
STDOUT = null;
isValid = false;
return;
}
isValid = true;
shellProcess = process;
STDIN = in;
STDOUT = out;
public static void registerShell(Context context) {
weakMm = new WeakReference<>(Utils.getMagiskManager(context));
}
public static Shell getShell() {
return new Shell();
}
MagiskManager mm = weakMm.get();
boolean needNewShell = mm.shell == null;
public static Shell getShell(String command) {
return new Shell(command);
}
public static Shell getShell(Context context) {
MagiskManager magiskManager = Utils.getMagiskManager(context);
if (magiskManager.shell == null || !magiskManager.shell.isValid) {
// Get new shell if needed
magiskManager.shell = getShell();
if (!needNewShell) {
try {
mm.shell.process.exitValue();
// The process is dead
needNewShell = true;
} catch (IllegalThreadStateException ignored) {
// This should be the expected result
}
}
return magiskManager.shell;
if (needNewShell) {
status = 1;
try {
mm.shell = new Shell("su --mount-master");
testRootShell(mm.shell);
} catch (IOException e) {
// Mount master not implemented
try {
mm.shell = new Shell("su");
testRootShell(mm.shell);
} catch (IOException e1) {
// No root exists
status = 0;
try {
mm.shell = new Shell("sh");
} catch (IOException e2) {
status = -1;
return null;
}
}
}
}
return mm.shell;
}
public static boolean rootAccess() {
return rootStatus > 0;
return status > 0;
}
public void run(Collection<String> output, String... commands) {
synchronized (process) {
StreamGobbler out = new StreamGobbler(STDOUT, output);
out.start();
run_raw(true, commands);
run_raw(true, "echo \'-shell-done-\'");
try { out.join(); } catch (InterruptedException ignored) {}
}
}
public void run_raw(boolean stdout, String... commands) {
synchronized (process) {
try {
for (String command : commands) {
Logger.shell(command);
STDIN.write((command + (stdout ? "\n" : " >/dev/null\n")).getBytes("UTF-8"));
STDIN.flush();
}
} catch (IOException e) {
e.printStackTrace();
process.destroy();
}
}
}
public void loadInputStream(InputStream in) {
@@ -146,67 +133,41 @@ public class Shell {
}
}
public List<String> sh(String... commands) {
public static List<String> sh(String... commands) {
List<String> res = new ArrayList<>();
if (!isValid) return res;
sh(res, commands);
return res;
}
public void sh_raw(String... commands) {
sh_raw(false, commands);
public static void sh(Collection<String> output, String... commands) {
Shell shell = getShell();
if (shell == null)
return;
shell.run(output, commands);
}
public void sh_raw(boolean stdout, String... commands) {
if (!isValid) return;
synchronized (shellProcess) {
try {
for (String command : commands) {
Logger.shell(command);
STDIN.write((command + (stdout ? "\n" : " >/dev/null\n")).getBytes("UTF-8"));
STDIN.flush();
}
} catch (IOException e) {
e.printStackTrace();
shellProcess.destroy();
isValid = false;
}
}
public static void sh_raw(String... commands) {
Shell shell = getShell();
if (shell == null)
return;
shell.run_raw(false, commands);
}
public void sh(Collection<String> output, String... commands) {
if (!isValid) return;
try {
shellProcess.exitValue();
isValid = false;
return; // The process is dead, return
} catch (IllegalThreadStateException ignored) {
// This should be the expected result
}
synchronized (shellProcess) {
StreamGobbler out = new StreamGobbler(STDOUT, output);
out.start();
sh_raw(true, commands);
sh_raw(true, "echo \'-shell-done-\'");
try { out.join(); } catch (InterruptedException ignored) {}
}
}
public List<String> su(String... commands) {
public static List<String> su(String... commands) {
if (!rootAccess()) return sh();
return sh(commands);
}
public void su_raw(String... commands) {
if (!rootAccess()) return;
sh_raw(commands);
}
public void su(Collection<String> output, String... commands) {
public static void su(Collection<String> output, String... commands) {
if (!rootAccess()) return;
sh(output, commands);
}
public static void su_raw(String... commands) {
if (!rootAccess()) return;
sh_raw(commands);
}
public static abstract class AbstractList<E> extends java.util.AbstractList<E> {
@Override
@@ -62,31 +62,31 @@ public class Utils {
private static final int MAGISK_UPDATE_NOTIFICATION_ID = 1;
private static final int APK_UPDATE_NOTIFICATION_ID = 2;
public static boolean itemExist(Shell shell, String path) {
public static boolean itemExist(String path) {
String command = "[ -e " + path + " ] && echo true || echo false";
List<String> ret = shell.su(command);
List<String> ret = Shell.su(command);
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
}
public static void createFile(Shell shell, String path) {
public static void createFile(String path) {
String folder = path.substring(0, path.lastIndexOf('/'));
String command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null;";
shell.su_raw(command);
Shell.su_raw(command);
}
public static void removeItem(Shell shell, String path) {
public static void removeItem(String path) {
String command = "rm -rf " + path + " 2>/dev/null";
shell.su_raw(command);
Shell.su_raw(command);
}
public static List<String> getModList(Shell shell, String path) {
public static List<String> getModList(String path) {
String command = "ls -d " + path + "/* | grep -v lost+found";
return shell.su(command);
return Shell.su(command);
}
public static List<String> readFile(Shell shell, String path) {
public static List<String> readFile(String path) {
String command = "cat " + path + " | sed '$a\\ ' | sed '$d'";
return shell.su(command);
return Shell.su(command);
}
public static void dlAndReceive(Context context, DownloadReceiver receiver, String link, String filename) {
@@ -225,24 +225,24 @@ public class Utils {
notificationManager.notify(APK_UPDATE_NOTIFICATION_ID, builder.build());
}
public static void enableMagiskHide(Shell shell) {
shell.su_raw("magiskhide --enable");
public static void enableMagiskHide() {
Shell.su_raw("magiskhide --enable");
}
public static void disableMagiskHide(Shell shell) {
shell.su_raw("magiskhide --disable");
public static void disableMagiskHide() {
Shell.su_raw("magiskhide --disable");
}
public static List<String> listMagiskHide(Shell shell) {
return shell.su("magiskhide --ls");
public static List<String> listMagiskHide() {
return Shell.su("magiskhide --ls");
}
public static void addMagiskHide(Shell shell, String pkg) {
shell.su_raw("magiskhide --add " + pkg);
public static void addMagiskHide(String pkg) {
Shell.su_raw("magiskhide --add " + pkg);
}
public static void rmMagiskHide(Shell shell, String pkg) {
shell.su_raw("magiskhide --rm " + pkg);
public static void rmMagiskHide(String pkg) {
Shell.su_raw("magiskhide --rm " + pkg);
}
public static String getLocaleString(Context context, Locale locale, @StringRes int id) {
@@ -331,7 +331,7 @@ public class Utils {
if (Shell.rootAccess()) {
options.add(mm.getString(R.string.direct_install));
}
List<String> res = Shell.getShell(mm).su("echo $SLOT");
List<String> res = Shell.su("echo $SLOT");
if (isValidShellResponse(res)) {
options.add(mm.getString(R.string.install_second_slot));
}
@@ -409,14 +409,14 @@ public class Utils {
if (slot[1] == 'a') slot[1] = 'b';
else slot[1] = 'a';
// Then find the boot image again
List<String> ret = Shell.getShell(mm).su(
List<String> ret = Shell.su(
"BOOTIMAGE=",
"SLOT=" + String.valueOf(slot),
"find_boot_image",
"echo \"$BOOTIMAGE\""
);
boot = isValidShellResponse(ret) ? ret.get(ret.size() - 1) : null;
Shell.getShell(mm).su_raw("mount_partitions");
Shell.su_raw("mount_partitions");
if (boot == null)
return;
receiver = new DownloadReceiver() {
@@ -497,12 +497,12 @@ public class Utils {
}
in.close();
out.close();
Shell.getShell(mm).su(
Shell.su(
"cat " + uninstaller + " > /cache/" + UNINSTALLER,
"cat " + utils + " > /data/magisk/" + UTIL_FUNCTIONS
);
mm.toast(R.string.uninstall_toast, Toast.LENGTH_LONG);
Shell.getShell(mm).su_raw(
Shell.su_raw(
"sleep 5",
"pm uninstall " + mm.getApplicationInfo().packageName
);