/* * This file is part of the RootTools Project: http://code.google.com/p/roottools/ * * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks * * This code is dual-licensed under the terms of the Apache License Version 2.0 and * the terms of the General Public License (GPL) Version 2. * You may use this code according to either of these licenses as is most appropriate * for your project on a case-by-case basis. * * The terms of each license can be found in the root directory of this project's repository as well as at: * * * http://www.apache.org/licenses/LICENSE-2.0 * * http://www.gnu.org/licenses/gpl-2.0.txt * * Unless required by applicable law or agreed to in writing, software * distributed under these Licenses is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See each License for the specific language governing permissions and * limitations under that License. */ /* *Special thanks to Jeremy Lakeman for the following code and for teaching me something new. * *Stephen */ package com.stericson.RootTools; 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; public class Shell { 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 String error = ""; private static final String token = "F*D^W@#FGF"; private static Shell rootShell = null; private static Shell shell = null; private static Shell customShell = null; private Shell(String cmd) throws IOException, TimeoutException { RootTools.log("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(5000); if (worker.exit == -911) { proc.destroy(); throw new TimeoutException(error); } 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 (customShell != null) return customShell; else if (rootShell != null) return rootShell; else return shell; } public static Shell startRootShell() throws IOException, TimeoutException { if (rootShell == null) { RootTools.log("Starting Root Shell!"); String cmd = "su"; // keep prompting the user until they accept, we hit 10 retries, or // the attempt fails quickly int retries = 0; while (rootShell == null) { try { rootShell = new Shell(cmd); } catch (IOException e) { if (retries++ >= 2) { RootTools.log("IOException, could not start shell"); throw e; } } } } else { RootTools.log("Using Existing Root Shell!"); } return rootShell; } public static Shell startCustomShell(String shellPath) throws IOException, TimeoutException { if (customShell == null) { RootTools.log("Starting Custom Shell!"); customShell = new Shell(shellPath); } else RootTools.log("Using Existing Custom Shell!"); return customShell; } public static Shell startShell() throws IOException, TimeoutException { if (shell == null) { RootTools.log("Starting Shell!"); shell = new Shell("/system/bin/sh"); } else RootTools.log("Using Existing Shell!"); return shell; } public static void runRootCommand(Command command) throws IOException, TimeoutException { startRootShell().add(command); } public static void runCommand(Command command) throws IOException, TimeoutException { startShell().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 closeShell() throws IOException { if (shell == null) return; shell.close(); } public static void closeAll() throws IOException { closeShell(); closeRootShell(); closeCustomShell(); } public static boolean isShellOpen() { if (shell == null) return false; else return true; } 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 (shell != null) return true; else 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) { RootTools.log(e.getMessage(), 2, e); } } }; 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(); RootTools.log("Closing shell"); return; } } } catch (InterruptedException e) { RootTools.log(e.getMessage(), 2, e); } } private Runnable output = new Runnable() { public void run() { try { readOutput(); } catch (IOException e) { RootTools.log(e.getMessage(), 2, e); } catch (InterruptedException e) { RootTools.log(e.getMessage(), 2, e); } } }; private void readOutput() throws IOException, InterruptedException { Command command = null; int read = 0; while (true) { String line = in.readLine(); // terminate on EOF if (line == null) break; // Log.v("Shell", "Out; \"" + line + "\""); if (command == null) { if (read >= commands.size()) { if (close) break; continue; } command = commands.get(read); } int pos = line.indexOf(token); if (pos > 0) command.output(command.id, line.substring(0, pos)); if (pos >= 0) { line = line.substring(pos); String fields[] = line.split(" "); if (fields.length >= 2 && fields[1] != null) { int id = Integer.parseInt(fields[1]); if (id == read) { command.setExitCode(Integer.parseInt(fields[2])); read++; command = null; continue; } } } command.output(command.id, line); } RootTools.log("Read all output"); proc.waitFor(); proc.destroy(); RootTools.log("Shell destroyed"); while (read < commands.size()) { if (command == null) command = commands.get(read); command.terminated("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 == shell) shell = 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.exitCode(); } } 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 (Exception e) { if (e.getMessage() != null) Shell.error = e.getMessage(); else Shell.error = "unkown error occured."; } } } }