package com.rincliu.library.common.persistence.rootmanager.container; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.EOFException; import java.io.IOException; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeoutException; import com.rincliu.library.common.persistence.rootmanager.exception.PermissionException; import com.rincliu.library.common.persistence.rootmanager.utils.RootUtils; public class Shell { private final static String TAG = Shell.class.getSimpleName(); private final Process proc; private final DataInputStream in; private final DataOutputStream out; private final List<Command> commands = new ArrayList<Command>(); private boolean close = false; private static int shellTimeout = 10000; private static String error = ""; private static final String token = "F*D^W@#FGF"; private static Shell rootShell = null; private static Shell customShell = null; private Shell(String cmd) throws IOException, TimeoutException, PermissionException { RootUtils.Log(TAG, "Starting shell: " + cmd); proc = new ProcessBuilder(cmd).redirectErrorStream(true).start(); in = new DataInputStream(proc.getInputStream()); out = new DataOutputStream(proc.getOutputStream()); Worker worker = new Worker(proc, in, out); worker.start(); try { worker.join(shellTimeout); if (worker.exit == -911) { proc.destroy(); throw new TimeoutException(error); } if (worker.exit == -42) { proc.destroy(); throw new PermissionException("Root Access Denied"); } else { new Thread(input, "Shell Input").start(); new Thread(output, "Shell Output").start(); } } catch (InterruptedException ex) { worker.interrupt(); Thread.currentThread().interrupt(); throw new TimeoutException(); } } public static Shell getOpenShell() { if (rootShell != null) { return rootShell; } else if (customShell != null) { return customShell; } else { return null; } } public static Shell startRootShell() throws IOException, TimeoutException, PermissionException { return Shell.startRootShell(shellTimeout); } public static Shell startRootShell(int timeout) throws IOException, TimeoutException, PermissionException { Shell.shellTimeout = timeout; if (rootShell == null) { RootUtils.Log("Starting Root Shell!"); String cmd = "su"; int retries = 0; while (rootShell == null) { try { rootShell = new Shell(cmd); } catch (IOException e) { if (retries++ >= 5) { RootUtils.Log("Could not start shell"); throw e; } } } } else { RootUtils.Log("Using Existing Root Shell!"); } return rootShell; } public static Shell startCustomShell(String shellPath) throws IOException, TimeoutException, PermissionException { return Shell.startCustomShell(shellPath, shellTimeout); } public static Shell startCustomShell(String shellPath, int timeout) throws IOException, TimeoutException, PermissionException { Shell.shellTimeout = timeout; if (customShell == null) { RootUtils.Log("Starting Custom Shell!"); customShell = new Shell(shellPath); } else { RootUtils.Log("Using Existing Custom Shell!"); } return customShell; } public static void runRootCommand(Command command) throws IOException, TimeoutException, PermissionException { startRootShell().add(command); } public static void closeCustomShell() throws IOException { if (customShell == null) { return; } customShell.close(); } public static void closeRootShell() throws IOException { if (rootShell == null) { return; } rootShell.close(); } public static void closeAll() throws IOException { closeRootShell(); closeCustomShell(); } public static boolean isCustomShellOpen() { if (customShell == null) { return false; } else { return true; } } public static boolean isRootShellOpen() { if (rootShell == null) { return false; } else { return true; } } public static boolean isAnyShellOpen() { if (rootShell != null) { return true; } else if (customShell != null) { return true; } else { return false; } } private Runnable input = new Runnable() { public void run() { try { writeCommands(); } catch (IOException e) { RootUtils.Log(e.getMessage()); } } }; private void writeCommands() throws IOException { try { int write = 0; while (true) { DataOutputStream out; synchronized (commands) { while (!close && write >= commands.size()) { commands.wait(); } out = this.out; } if (write < commands.size()) { Command next = commands.get(write); next.writeCommand(out); String line = "\necho " + token + " " + write + " $?\n"; out.write(line.getBytes()); out.flush(); write++; } else if (close) { out.write("\nexit 0\n".getBytes()); out.flush(); out.close(); RootUtils.Log("Closing shell"); return; } } } catch (InterruptedException e) { e.printStackTrace(); } } private Runnable output = new Runnable() { public void run() { try { readOutput(); } catch (IOException e) { e.printStackTrace(); } catch (InterruptedException e) { e.printStackTrace(); } } }; private void readOutput() throws IOException, InterruptedException { Command command = null; int read = 0; while (true) { String line = in.readLine(); if (line == null) { break; } if (command == null) { if (read >= commands.size()) { if (close) { break; } continue; } command = commands.get(read); } int pos = line.indexOf(token); if (pos > 0) { command.onUpdate(command.getID(), line.substring(0, pos)); } if (pos >= 0) { line = line.substring(pos); String fields[] = line.split(" "); if (fields.length >= 2 && fields[1] != null) { int id = 0; try { id = Integer.parseInt(fields[1]); } catch (NumberFormatException e) {} int exitCode = -1; try { exitCode = Integer.parseInt(fields[2]); } catch (NumberFormatException e) {} if (id == read) { command.setExitCode(exitCode); read++; command = null; continue; } } } command.onUpdate(command.getID(), line); } RootUtils.Log("Read all output"); proc.waitFor(); proc.destroy(); RootUtils.Log("Shell destroyed"); while (read < commands.size()) { if (command == null) { command = commands.get(read); } command.terminate("Unexpected Termination."); command = null; read++; } } public Command add(Command command) throws IOException { if (close) throw new IllegalStateException("Unable to add commands to a closed shell"); synchronized (commands) { commands.add(command); commands.notifyAll(); } return command; } public void close() throws IOException { if (this == rootShell) { rootShell = null; } if (this == customShell) { customShell = null; } synchronized (commands) { this.close = true; commands.notifyAll(); } } public int countCommands() { return commands.size(); } public void waitFor() throws IOException, InterruptedException { close(); if (commands.size() > 0) { Command command = commands.get(commands.size() - 1); command.waitForFinish(); } } protected static class Worker extends Thread { public int exit = -911; public Process proc; public DataInputStream in; public DataOutputStream out; private Worker(Process proc, DataInputStream in, DataOutputStream out) { this.proc = proc; this.in = in; this.out = out; } public void run() { try { out.write("echo Started\n".getBytes()); out.flush(); while (true) { String line = in.readLine(); if (line == null) { throw new EOFException(); } if ("".equals(line)) { continue; } if ("Started".equals(line)) { this.exit = 1; break; } Shell.error = "unkown error occured."; } } catch (IOException e) { exit = -42; if (e.getMessage() != null) { Shell.error = e.getMessage(); } else { Shell.error = "RootAccess denied?."; } } } } }