package com.topjohnwu.magisk.utils; import android.text.TextUtils; import com.topjohnwu.magisk.MagiskManager; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.List; /** * Modified by topjohnwu, based on Chainfire's libsuperuser */ public class Shell { // -2 = not initialized; -1 = no shell; 0 = non root shell; 1 = root shell public static int status = -2; private final Process process; private final OutputStream STDIN; private final InputStream STDOUT; 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")) { shell.STDIN.close(); shell.STDIN.close(); throw new IOException(); } } public Shell(String command) throws IOException { process = Runtime.getRuntime().exec(command); STDIN = process.getOutputStream(); STDOUT = process.getInputStream(); } public static Shell getShell() { MagiskManager mm = MagiskManager.get(); boolean needNewShell = mm.shell == null; if (!needNewShell) { try { mm.shell.process.exitValue(); // The process is dead needNewShell = true; } catch (IllegalThreadStateException ignored) { // This should be the expected result } } 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; } } } if (rootAccess()) { // Load utility shell scripts try (InputStream in = mm.getAssets().open(Const.UTIL_FUNCTIONS)) { mm.shell.loadInputStream(in); } catch (IOException e) { e.printStackTrace(); } // Root shell initialization mm.shell.run_raw(false, "export PATH=" + Const.BUSYBOXPATH + ":$PATH", "mount_partitions", "find_boot_image", "migrate_boot_backup" ); } } return mm.shell; } public static boolean rootAccess() { if (status == -2) getShell(); return status > 0; } public void run(Collection output, String... commands) { synchronized (process) { try { StreamGobbler out = new StreamGobbler(STDOUT, output); out.start(); run_raw(true, commands); STDIN.write("echo \'-shell-done-\'\n".getBytes("UTF-8")); STDIN.flush(); try { out.join(); } catch (InterruptedException ignored) {} } catch (IOException e) { e.printStackTrace(); process.destroy(); } } } public void run_raw(boolean stdout, String... commands) { synchronized (process) { try { for (String command : commands) { Logger.shell(true, 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) { synchronized (process) { try { int read; byte[] bytes = new byte[4096]; while ((read = in.read(bytes)) != -1) { STDIN.write(bytes, 0, read); } STDIN.flush(); } catch (IOException e) { e.printStackTrace(); } } } public static List sh(String... commands) { List res = new ArrayList<>(); sh(res, commands); return res; } public static void sh(Collection output, String... commands) { Shell shell = getShell(); if (shell == null) return; shell.run(output, commands); } public static void sh_raw(String... commands) { Shell shell = getShell(); if (shell == null) return; shell.run_raw(false, commands); } public static List su(String... commands) { if (!rootAccess()) return sh(); return sh(commands); } public static void su(Collection 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 extends java.util.AbstractList { @Override public abstract boolean add(E e); @Override public E get(int i) { return null; } @Override public int size() { return 0; } } }