Android Open Source - Mountie Shell






From Project

Back to project page Mountie.

License

The source code is released under:

GNU General Public License

If you think the Android project Mountie listed in this page is inappropriate, such as containing malicious code/tools or violating the copyright, please email info at java2s dot com, thanks.

Java Source Code

/* 
 * This file is part of the RootTools Project: http://code.google.com/p/roottools/
 *  //from w  w  w .  j av a2s . c  o  m
 * 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.
 */
package com.stericson.RootTools.execution;

import android.content.Context;

import com.stericson.RootTools.RootTools;
import com.stericson.RootTools.exceptions.RootDeniedException;

import java.io.BufferedReader;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeoutException;

public class Shell {

    public static enum ShellType {
        NORMAL,
        ROOT,
        CUSTOM
    }

    //this is only used with root shells
    public static enum ShellContext {
        NORMAL("normal"), //The normal context...
        SHELL("u:r:shell:s0"), //Unpriviliged shell (such as an adb shell)
        SYSTEM_SERVER("u:r:system_server:s0"), // system_server, u:r:system:s0 on some firmwares
        SYSTEM_APP("u:r:system_app:s0"), // System apps
        PLATFORM_APP("u:r:platform_app:s0"), // System apps
        UNTRUSTED_APP("u:r:untrusted_app:s0"), // Third-party apps
        RECOVERY("u:r:recovery:s0"); //Recovery

        private String value;

        private ShellContext(String value)
        {
            this.value = value;
        }

        public String getValue() {
            return this.value;
        }

    }

    //Statics -- visible to all
    private static final String token = "F*D^W@#FGF";
    private static Shell rootShell = null;
    private static Shell shell = null;
    private static Shell customShell = null;

    //the default context for root shells...
    public static ShellContext defaultContext = ShellContext.NORMAL;

    //per shell
    private int shellTimeout = 25000;
    private ShellType shellType = null;
    private ShellContext shellContext = Shell.ShellContext.NORMAL;

    private String error = "";

    private final Process proc;
    private final BufferedReader in;
    private final OutputStreamWriter out;
    private final List<Command> commands = new ArrayList<Command>();

    //indicates whether or not to close the shell
    private boolean close = false;

    public boolean isExecuting = false;
    public boolean isReading = false;

    private int maxCommands = 5000;
    private int read = 0;
    private int write = 0;
    private int totalExecuted = 0;
    private int totalRead = 0;
    private boolean isCleaning = false;

    private Shell(String cmd, ShellType shellType, ShellContext shellContext, int shellTimeout) throws IOException, TimeoutException, RootDeniedException {

        RootTools.log("Starting shell: " + cmd);
        RootTools.log("Context: " + shellContext.getValue());
        RootTools.log("Timeout: " + shellTimeout);

        this.shellType = shellType;
        this.shellTimeout = shellTimeout > 0 ? shellTimeout : this.shellTimeout;
        this.shellContext = shellContext;

        if(this.shellContext == ShellContext.NORMAL)
        {
            this.proc = new ProcessBuilder(cmd).redirectErrorStream(true).start();
        }
        else
        {
            //only done for root shell...
            this.proc = new ProcessBuilder(cmd, "--context " + this.shellContext.getValue()).redirectErrorStream(true).start();
        }

        this.in = new BufferedReader(new InputStreamReader(this.proc.getInputStream(), "UTF-8"));
        this.out = new OutputStreamWriter(this.proc.getOutputStream(), "UTF-8");

        /**
         * Thread responsible for carrying out the requested operations
         */
        Worker worker = new Worker(this);
        worker.start();

        try {
            /**
             * The flow of execution will wait for the thread to die or wait until the
             * given timeout has expired.
             *
             * The result of the worker, which is determined by the exit code of the worker,
             * will tell us if the operation was completed successfully or it the operation
             * failed.
             */
            worker.join(this.shellTimeout);

            /**
             * The operation could not be completed before the timeout occured.
             */
            if (worker.exit == -911) {

                try {
                    this.proc.destroy();
                } catch (Exception e) {}

                closeQuietly(this.in);
                closeQuietly(this.out);

                throw new TimeoutException(this.error);
            }
            /**
             * Root access denied?
             */
            else if (worker.exit == -42) {

                try {
                    this.proc.destroy();
                } catch (Exception e) {}

                closeQuietly(this.in);
                closeQuietly(this.out);

                throw new RootDeniedException("Root Access Denied");
            }
            /**
             * Normal exit
             */
            else {
                /**
                 * The shell is open.
                 *
                 * Start two threads, one to handle the input and one to handle the output.
                 *
                 * input, and output are runnables that the threads execute.
                 */
                Thread si = new Thread(this.input, "Shell Input");
                si.setPriority(Thread.NORM_PRIORITY);
                si.start();

                Thread so = new Thread(this.output, "Shell Output");
                so.setPriority(Thread.NORM_PRIORITY);
                so.start();
            }
        } catch (InterruptedException ex) {
            worker.interrupt();
            Thread.currentThread().interrupt();
            throw new TimeoutException();
        }
    }


    public Command add(Command command) throws IOException {
        if (this.close)
            throw new IllegalStateException(
                    "Unable to add commands to a closed shell");

        while (this.isCleaning) {
            //Don't add commands while cleaning
            ;
        }
        this.commands.add(command);

        this.notifyThreads();

        return command;
    }

    public void useCWD(Context context) throws IOException, TimeoutException, RootDeniedException {
        add(
                new CommandCapture(
                        -1,
                        false,
                        "cd " + context.getApplicationInfo().dataDir)
        );
    }

    private void cleanCommands() {
        this.isCleaning = true;
        int toClean = Math.abs(this.maxCommands - (this.maxCommands / 4));
        RootTools.log("Cleaning up: " + toClean);

        for (int i = 0; i < toClean; i++) {
            this.commands.remove(0);
        }

        this.read = this.commands.size() - 1;
        this.write = this.commands.size() - 1;
        this.isCleaning = false;
    }

    private void closeQuietly(final Reader input) {
        try {
            if (input != null) {
                input.close();
            }
        } catch (Exception ignore) {}
    }

    private void closeQuietly(final Writer output) {
        try {
            if (output != null) {
                output.close();
            }
        } catch (Exception ignore) {}
    }

    public void close() throws IOException {
        if (this == Shell.rootShell)
            Shell.rootShell = null;
        else if (this == Shell.shell)
            Shell.shell = null;
        else if (this == Shell.customShell)
            Shell.customShell = null;
        synchronized (this.commands) {
            /**
             * instruct the two threads monitoring input and output
             * of the shell to close.
             */
            this.close = true;
            this.notifyThreads();
        }
    }

    public static void closeCustomShell() throws IOException {
        if (Shell.customShell == null)
            return;
        Shell.customShell.close();
    }

    public static void closeRootShell() throws IOException {
        if (Shell.rootShell == null)
            return;
        Shell.rootShell.close();
    }

    public static void closeShell() throws IOException {
        if (Shell.shell == null)
            return;
        Shell.shell.close();
    }

    public static void closeAll() throws IOException {
        Shell.closeShell();
        Shell.closeRootShell();
        Shell.closeCustomShell();
    }

    public int getCommandQueuePosition(Command cmd) {
        return this.commands.indexOf(cmd);
    }

    public String getCommandQueuePositionString(Command cmd) {
        return "Command is in position " + getCommandQueuePosition(cmd) + " currently executing command at position " + this.write + " and the number of commands is " + commands.size();
    }

    public static Shell getOpenShell() {
        if (Shell.customShell != null)
            return Shell.customShell;
        else if (Shell.rootShell != null)
            return Shell.rootShell;
        else
            return Shell.shell;
    }

    public static boolean isShellOpen() {
        return Shell.shell == null;
    }

    public static boolean isCustomShellOpen() {
        return Shell.customShell == null;
    }

    public static boolean isRootShellOpen() {
        return Shell.rootShell == null;
    }

    public static boolean isAnyShellOpen() {
        return Shell.shell != null || Shell.rootShell != null || Shell.customShell != null;
    }

    /**
     * Runnable to write commands to the open shell.
     * <p/>
     * When writing commands we stay in a loop and wait for new
     * commands to added to "commands"
     * <p/>
     * The notification of a new command is handled by the method add in this class
     */
    private Runnable input = new Runnable() {
        public void run() {

            try {
                while (true) {

                    synchronized (commands) {
                        /**
                         * While loop is used in the case that notifyAll is called
                         * and there are still no commands to be written, a rare
                         * case but one that could happen.
                         */
                        while (!close && write >= commands.size()) {
                            isExecuting = false;
                            commands.wait();
                        }
                    }

                    if (write >= maxCommands) {

                        /**
                         * wait for the read to catch up.
                         */
                        while (read != write)
                        {
                            RootTools.log("Waiting for read and write to catch up before cleanup.");
                        }
                        /**
                         * Clean up the commands, stay neat.
                         */
                        cleanCommands();
                    }

                    /**
                     * Write the new command
                     *
                     * We write the command followed by the token to indicate
                     * the end of the command execution
                     */
                    if (write < commands.size()) {
                        isExecuting = true;
                        Command cmd = commands.get(write);
                        cmd.startExecution();
                        RootTools.log("Executing: " + cmd.getCommand());

                        out.write(cmd.getCommand());
                        String line = "\necho " + token + " " + totalExecuted + " $?\n";
                        out.write(line);
                        out.flush();
                        write++;
                        totalExecuted++;
                    } else if (close) {
                        /**
                         * close the thread, the shell is closing.
                         */
                        isExecuting = false;
                        out.write("\nexit 0\n");
                        out.flush();
                        RootTools.log("Closing shell");
                        return;
                    }
                }
            } catch (IOException e) {
                RootTools.log(e.getMessage(), 2, e);
            } catch (InterruptedException e) {
                RootTools.log(e.getMessage(), 2, e);
            } finally {
                write = 0;
                closeQuietly(out);
            }
        }
    };

    protected void notifyThreads() {
        Thread t = new Thread() {
            public void run() {
                synchronized (commands) {
                    commands.notifyAll();
                }
            }
        };

        t.start();
    }

    /**
     * Runnable to monitor the responses from the open shell.
     */
    private Runnable output = new Runnable() {
        public void run() {
            try {
                Command command = null;

                while (!close) {
                    isReading = false;
                    String line = in.readLine();
                    isReading = true;

                    /**
                     * If we recieve EOF then the shell closed
                     */
                    if (line == null)
                        break;

                    if (command == null) {
                        if (read >= commands.size()) {
                            if (close)
                                break;

                            continue;
                        }
                        command = commands.get(read);
                    }

                    /**
                     * trying to determine if all commands have been completed.
                     *
                     * if the token is present then the command has finished execution.
                     */
                    int pos = line.indexOf(token);


                    if (pos == -1) {
                        /**
                         * send the output for the implementer to process
                         */
                        command.output(command.id, line);
                    }
                    if (pos > 0) {
                      /**
                       * token is suffix of output, send output part to implementer
                       */
                      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 = 0;

                            try {
                                id = Integer.parseInt(fields[1]);
                            } catch (NumberFormatException e) {
                            }

                            int exitCode = -1;

                            try {
                                exitCode = Integer.parseInt(fields[2]);
                            } catch (NumberFormatException e) {
                            }

                            if (id == totalRead) {
                                command.setExitCode(exitCode);
                                command.commandFinished();
                                command = null;

                                read++;
                                totalRead++;
                                continue;
                            }
                        }
                    }
                }

                RootTools.log("Read all output");
                try {
                    proc.waitFor();
                    proc.destroy();
                } catch (Exception e) {}

                closeQuietly(out);
                closeQuietly(in);

                RootTools.log("Shell destroyed");

                while (read < commands.size()) {
                    if (command == null)
                        command = commands.get(read);

                    command.terminated("Unexpected Termination.");
                    command = null;
                    read++;
                }

                read = 0;

            } catch (IOException e) {
                RootTools.log(e.getMessage(), 2, e);
            }
        }
    };

    public static void runRootCommand(Command command) throws IOException, TimeoutException, RootDeniedException {
        Shell.startRootShell().add(command);
    }

    public static void runCommand(Command command) throws IOException, TimeoutException {
        Shell.startShell().add(command);
    }

    public static Shell startRootShell() throws IOException, TimeoutException, RootDeniedException {
        return Shell.startRootShell(0, 3);
    }

    public static Shell startRootShell(int timeout) throws IOException, TimeoutException, RootDeniedException {
        return Shell.startRootShell(timeout, 3);
    }

    public static Shell startRootShell(int timeout, int retry) throws IOException, TimeoutException, RootDeniedException {
        return Shell.startRootShell(timeout, Shell.defaultContext, retry);
    }

    public static Shell startRootShell(int timeout, ShellContext shellContext, int retry) throws IOException, TimeoutException, RootDeniedException {

        if (Shell.rootShell == null) {

            RootTools.log("Starting Root Shell!");
            String cmd = "su";
            // keep prompting the user until they accept for x amount of times...
            int retries = 0;
            while (Shell.rootShell == null) {
                try {
                    Shell.rootShell = new Shell(cmd, ShellType.ROOT, shellContext, timeout);
                } catch (IOException e) {
                    if (retries++ >= retry) {
                        RootTools.log("IOException, could not start shell");
                        throw e;
                    }
                }
            }
        }
        else if (Shell.rootShell.shellContext != shellContext) {
            try {
                RootTools.log("Context is different than open shell, switching context...");
                Shell.rootShell.switchRootShellContext(shellContext);
            } catch (IOException e) {
                RootTools.log("Context could not be switched for existing root shell...");
                throw e;
            }
        } else {
            RootTools.log("Using Existing Root Shell!");
        }

        return Shell.rootShell;
    }

    public static Shell startCustomShell(String shellPath) throws IOException, TimeoutException, RootDeniedException {
        return Shell.startCustomShell(shellPath, 0);
    }

    public static Shell startCustomShell(String shellPath, int timeout) throws IOException, TimeoutException, RootDeniedException {

        if (Shell.customShell == null) {
            RootTools.log("Starting Custom Shell!");
            Shell.customShell = new Shell(shellPath, ShellType.CUSTOM, ShellContext.NORMAL, timeout);
        } else
            RootTools.log("Using Existing Custom Shell!");

        return Shell.customShell;
    }

    public static Shell startShell() throws IOException, TimeoutException {
        return Shell.startShell(0);
    }

    public static Shell startShell(int timeout) throws IOException, TimeoutException {

        try {
            if (Shell.shell == null) {
                RootTools.log("Starting Shell!");
                Shell.shell = new Shell("/system/bin/sh", ShellType.NORMAL, ShellContext.NORMAL, timeout);
            } else
                RootTools.log("Using Existing Shell!");
            return Shell.shell;
        } catch (RootDeniedException e) {
            //Root Denied should never be thrown.
            throw new IOException();
        }
    }

    public Shell switchRootShellContext(ShellContext shellContext) throws IOException, TimeoutException, RootDeniedException {
        if(this.shellType == ShellType.ROOT)
        {
            try {
                Shell.closeRootShell();
            } catch(Exception e) {
                RootTools.log("Problem closing shell while trying to switch context...");
            }

            //create new root shell with new context...
            return Shell.startRootShell(this.shellTimeout, shellContext, 3);
        }
        else
        {
            //can only switch context on a root shell...
            RootTools.log("Can only switch context on a root shell!");
            return this;
        }
    }

    protected static class Worker extends Thread {
        public int exit = -911;

        public Shell shell;

        private Worker(Shell shell) {
            this.shell = shell;
        }

        public void run() {

            /**
             * Trying to open the shell.
             *
             * We echo "Started" and we look for it in the output.
             *
             * If we find the output then the shell is open and we return.
             *
             * If we do not find it then we determine the error and report
             * it by setting the value of the variable exit
             */
            try {
                shell.out.write("echo Started\n");
                shell.out.flush();

                while (true) {
                    String line = shell.in.readLine();
                    if (line == null) {
                        throw new EOFException();
                    }
                    if ("".equals(line))
                        continue;
                    if ("Started".equals(line)) {
                        this.exit = 1;
                        setShellOom();
                        break;
                    }

                    shell.error = "unkown error occured.";
                }
            } catch (IOException e) {
                exit = -42;
                if (e.getMessage() != null)
                    shell.error = e.getMessage();
                else
                    shell.error = "RootAccess denied?.";
            }

        }

        /*
         * setOom for shell processes (sh and su if root shell)
         * and discard outputs
         * 
         */
        private void setShellOom() {
      try {
        Class<?> processClass = shell.proc.getClass();
        Field field;
        try {
          field = processClass.getDeclaredField("pid");
        } catch (NoSuchFieldException e) {
          field = processClass.getDeclaredField("id");
        }
        field.setAccessible(true);
        int pid = (Integer) field.get(shell.proc);
                shell.out.write("(echo -17 > /proc/" + pid + "/oom_adj) &> /dev/null\n");
                shell.out.write("(echo -17 > /proc/$$/oom_adj) &> /dev/null\n");
                shell.out.flush();
      } catch (Exception e) {
                e.printStackTrace();
      }
    }
    }
}




Java Source Code List

com.morlunk.mountie.ApplicationTest.java
com.morlunk.mountie.Automounter.java
com.morlunk.mountie.Constants.java
com.morlunk.mountie.MountieActivity.java
com.morlunk.mountie.MountieBootReceiver.java
com.morlunk.mountie.MountieNotification.java
com.morlunk.mountie.MountieService.java
com.morlunk.mountie.UsbHotplugReceiver.java
com.morlunk.mountie.command.BlkidCommand.java
com.morlunk.mountie.fs.BlockDeviceObserver.java
com.morlunk.mountie.fs.BlockDevice.java
com.morlunk.mountie.fs.MountException.java
com.morlunk.mountie.fs.MountListener.java
com.morlunk.mountie.fs.Mount.java
com.morlunk.mountie.fs.PartitionListener.java
com.morlunk.mountie.fs.Partition.java
com.morlunk.mountie.fs.UnmountListener.java
com.morlunk.mountie.fs.Volume.java
com.morlunk.mountie.jni.Native.java
com.morlunk.mountie.util.Filesystems.java
com.stericson.RootTools.Constants.java
com.stericson.RootTools.RootTools.java
com.stericson.RootTools.containers.Mount.java
com.stericson.RootTools.containers.Permissions.java
com.stericson.RootTools.containers.RootClass.java
com.stericson.RootTools.containers.Symlink.java
com.stericson.RootTools.exceptions.RootDeniedException.java
com.stericson.RootTools.execution.CommandCapture.java
com.stericson.RootTools.execution.Command.java
com.stericson.RootTools.execution.JavaCommandCapture.java
com.stericson.RootTools.execution.Shell.java
com.stericson.RootTools.internal.Installer.java
com.stericson.RootTools.internal.InternalVariables.java
com.stericson.RootTools.internal.Remounter.java
com.stericson.RootTools.internal.RootToolsInternalMethods.java
com.stericson.RootTools.internal.Runner.java