├── .gitignore ├── README.md ├── build.gradle ├── gradle.properties ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── proguard-rules.pro ├── settings.gradle └── src ├── androidTest └── java │ └── com │ └── example │ └── RootTools │ └── ApplicationTest.java ├── main ├── AndroidManifest.xml ├── java │ └── com │ │ └── stericson │ │ └── RootTools │ │ ├── Constants.java │ │ ├── RootTools.java │ │ ├── SanityCheckRootTools.java │ │ ├── containers │ │ ├── Mount.java │ │ ├── Permissions.java │ │ └── Symlink.java │ │ └── internal │ │ ├── Installer.java │ │ ├── InternalVariables.java │ │ ├── Remounter.java │ │ ├── RootToolsInternalMethods.java │ │ └── Runner.java └── res │ └── values │ └── strings.xml └── test └── java └── com └── example └── roottools └── ExampleUnitTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | # Gradle files 2 | .gradle/ 3 | build/ 4 | 5 | # Local configuration file (sdk path, etc) 6 | local.properties 7 | 8 | # Android Studio generated folders 9 | .navigation/ 10 | captures/ 11 | .externalNativeBuild 12 | 13 | # IntelliJ project files 14 | *.iml 15 | .idea/ 16 | 17 | # Misc 18 | .DS_Store 19 | 20 | # Keystore files 21 | *.jks 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # RootTools 2 | RootTools Library 3 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | buildscript { 2 | repositories { 3 | google() 4 | jcenter() 5 | } 6 | 7 | repositories { 8 | maven { url 'http://repo1.maven.org/maven2' } 9 | } 10 | 11 | dependencies { 12 | classpath 'com.android.tools.build:gradle:3.1.3' 13 | classpath 'com.github.dcendents:android-maven-gradle-plugin:1.5' 14 | } 15 | } 16 | 17 | repositories { 18 | google() 19 | jcenter() 20 | maven { url "https://jitpack.io" } 21 | } 22 | 23 | apply plugin: 'com.android.library' 24 | 25 | android { 26 | compileSdkVersion 25 27 | 28 | defaultConfig { 29 | minSdkVersion 11 30 | targetSdkVersion 25 31 | versionName project.version 32 | } 33 | 34 | compileOptions { 35 | sourceCompatibility JavaVersion.VERSION_1_7 36 | targetCompatibility JavaVersion.VERSION_1_7 37 | } 38 | } 39 | 40 | dependencies { 41 | testImplementation 'junit:junit:4.12' 42 | implementation 'com.android.support:appcompat-v7:25.3.1' 43 | implementation 'com.github.Stericson:RootShell:1.6' 44 | } 45 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Stericson/RootTools/4fb932c9703893a62a801414bc97710c8f166d92/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jul 04 17:42:21 CEST 2018 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.8-all.zip 7 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn ( ) { 37 | echo "$*" 38 | } 39 | 40 | die ( ) { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save ( ) { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # By default, the flags in this file are appended to flags specified 3 | # in ${sdk.dir}/tools/proguard/proguard-android.txt 4 | # You can edit the include path and order by changing the proguardFiles 5 | # directive in build.gradle. 6 | # 7 | # For more details, see 8 | # http://developer.android.com/guide/developing/tools/proguard.html 9 | 10 | # Add any project specific keep options here: 11 | 12 | # If your project uses WebView with JS, uncomment the following 13 | # and specify the fully qualified class name to the JavaScript interface 14 | # class: 15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 16 | # public *; 17 | #} 18 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'RootTools' 2 | include ':demo' 3 | -------------------------------------------------------------------------------- /src/androidTest/java/com/example/RootTools/ApplicationTest.java: -------------------------------------------------------------------------------- 1 | package com.example.RootTools; 2 | 3 | import android.app.Application; 4 | import android.test.ApplicationTestCase; 5 | 6 | /** 7 | * Testing Fundamentals 8 | */ 9 | public class ApplicationTest extends ApplicationTestCase { 10 | public ApplicationTest() { 11 | super(Application.class); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/Constants.java: -------------------------------------------------------------------------------- 1 | package com.stericson.RootTools; 2 | 3 | public class Constants 4 | { 5 | public static final String TAG = "RootTools v5.1"; 6 | public static final int FPS = 1; 7 | public static final int BBA = 3; 8 | public static final int BBV = 4; 9 | public static final int GI = 5; 10 | public static final int GS = 6; 11 | public static final int GSYM = 7; 12 | public static final int GET_MOUNTS = 8; 13 | public static final int GET_SYMLINKS = 9; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/RootTools.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the RootTools Project: http://code.google.com/p/RootTools/ 3 | * 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks 5 | * 6 | * This code is dual-licensed under the terms of the Apache License Version 2.0 and 7 | * the terms of the General Public License (GPL) Version 2. 8 | * You may use this code according to either of these licenses as is most appropriate 9 | * for your project on a case-by-case basis. 10 | * 11 | * The terms of each license can be found in the root directory of this project's repository as well as at: 12 | * 13 | * * http://www.apache.org/licenses/LICENSE-2.0 14 | * * http://www.gnu.org/licenses/gpl-2.0.txt 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under these Licenses is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See each License for the specific language governing permissions and 20 | * limitations under that License. 21 | */ 22 | 23 | package com.stericson.RootTools; 24 | 25 | import android.app.Activity; 26 | import android.content.Context; 27 | import android.content.Intent; 28 | import android.util.Log; 29 | 30 | import com.stericson.RootShell.RootShell; 31 | import com.stericson.RootShell.exceptions.RootDeniedException; 32 | import com.stericson.RootShell.execution.Command; 33 | import com.stericson.RootShell.execution.Shell; 34 | import com.stericson.RootTools.containers.Mount; 35 | import com.stericson.RootTools.containers.Permissions; 36 | import com.stericson.RootTools.containers.Symlink; 37 | import com.stericson.RootTools.internal.Remounter; 38 | import com.stericson.RootTools.internal.RootToolsInternalMethods; 39 | import com.stericson.RootTools.internal.Runner; 40 | 41 | import java.io.IOException; 42 | import java.util.ArrayList; 43 | import java.util.Arrays; 44 | import java.util.List; 45 | import java.util.concurrent.TimeoutException; 46 | 47 | public final class RootTools { 48 | 49 | /** 50 | * This class is the gateway to every functionality within the RootTools library.The developer 51 | * should only have access to this class and this class only.This means that this class should 52 | * be the only one to be public.The rest of the classes within this library must not have the 53 | * public modifier. 54 | *

55 | * All methods and Variables that the developer may need to have access to should be here. 56 | *

57 | * If a method, or a specific functionality, requires a fair amount of code, or work to be done, 58 | * then that functionality should probably be moved to its own class and the call to it done 59 | * here.For examples of this being done, look at the remount functionality. 60 | */ 61 | 62 | private static RootToolsInternalMethods rim = null; 63 | 64 | public static void setRim(RootToolsInternalMethods rim) { 65 | RootTools.rim = rim; 66 | } 67 | 68 | private static final RootToolsInternalMethods getInternals() { 69 | if (rim == null) { 70 | RootToolsInternalMethods.getInstance(); 71 | return rim; 72 | } else { 73 | return rim; 74 | } 75 | } 76 | 77 | // -------------------- 78 | // # Public Variables # 79 | // -------------------- 80 | 81 | public static boolean debugMode = false; 82 | public static String utilPath; 83 | 84 | /** 85 | * Setting this to false will disable the handler that is used 86 | * by default for the 3 callback methods for Command. 87 | *

88 | * By disabling this all callbacks will be called from a thread other than 89 | * the main UI thread. 90 | */ 91 | public static boolean handlerEnabled = true; 92 | 93 | 94 | /** 95 | * Setting this will change the default command timeout. 96 | *

97 | * The default is 20000ms 98 | */ 99 | public static int default_Command_Timeout = 20000; 100 | 101 | 102 | // --------------------------- 103 | // # Public Variable Getters # 104 | // --------------------------- 105 | 106 | // ------------------ 107 | // # Public Methods # 108 | // ------------------ 109 | 110 | /** 111 | * This will check a given binary, determine if it exists and determine that it has either the 112 | * permissions 755, 775, or 777. 113 | * 114 | * @param util Name of the utility to check. 115 | * @return boolean to indicate whether the binary is installed and has appropriate permissions. 116 | */ 117 | public static boolean checkUtil(String util) { 118 | 119 | return getInternals().checkUtil(util); 120 | } 121 | 122 | /** 123 | * This will close all open shells. 124 | * 125 | * @throws IOException 126 | */ 127 | public static void closeAllShells() throws IOException { 128 | RootShell.closeAllShells(); 129 | } 130 | 131 | /** 132 | * This will close the custom shell that you opened. 133 | * 134 | * @throws IOException 135 | */ 136 | public static void closeCustomShell() throws IOException { 137 | RootShell.closeCustomShell(); 138 | } 139 | 140 | /** 141 | * This will close either the root shell or the standard shell depending on what you specify. 142 | * 143 | * @param root a boolean to specify whether to close the root shell or the standard shell. 144 | * @throws IOException 145 | */ 146 | public static void closeShell(boolean root) throws IOException { 147 | RootShell.closeShell(root); 148 | } 149 | 150 | /** 151 | * Copys a file to a destination. Because cp is not available on all android devices, we have a 152 | * fallback on the cat command 153 | * 154 | * @param source example: /data/data/org.adaway/files/hosts 155 | * @param destination example: /system/etc/hosts 156 | * @param remountAsRw remounts the destination as read/write before writing to it 157 | * @param preserveFileAttributes tries to copy file attributes from source to destination, if only cat is available 158 | * only permissions are preserved 159 | * @return true if it was successfully copied 160 | */ 161 | public static boolean copyFile(String source, String destination, boolean remountAsRw, 162 | boolean preserveFileAttributes) { 163 | return getInternals().copyFile(source, destination, remountAsRw, preserveFileAttributes); 164 | } 165 | 166 | /** 167 | * Deletes a file or directory 168 | * 169 | * @param target example: /data/data/org.adaway/files/hosts 170 | * @param remountAsRw remounts the destination as read/write before writing to it 171 | * @return true if it was successfully deleted 172 | */ 173 | public static boolean deleteFileOrDirectory(String target, boolean remountAsRw) { 174 | return getInternals().deleteFileOrDirectory(target, remountAsRw); 175 | } 176 | 177 | /** 178 | * Use this to check whether or not a file exists on the filesystem. 179 | * 180 | * @param file String that represent the file, including the full path to the 181 | * file and its name. 182 | * @return a boolean that will indicate whether or not the file exists. 183 | */ 184 | public static boolean exists(final String file) { 185 | return exists(file, false); 186 | } 187 | 188 | /** 189 | * Use this to check whether or not a file OR directory exists on the filesystem. 190 | * 191 | * @param file String that represent the file OR the directory, including the full path to the 192 | * file and its name. 193 | * @param isDir boolean that represent whether or not we are looking for a directory 194 | * @return a boolean that will indicate whether or not the file exists. 195 | */ 196 | public static boolean exists(final String file, boolean isDir) { 197 | return RootShell.exists(file, isDir); 198 | } 199 | 200 | /** 201 | * This will try and fix a given binary. (This is for Busybox applets or Toolbox applets) By 202 | * "fix", I mean it will try and symlink the binary from either toolbox or Busybox and fix the 203 | * permissions if the permissions are not correct. 204 | * 205 | * @param util Name of the utility to fix. 206 | * @param utilPath path to the toolbox that provides ln, rm, and chmod. This can be a blank string, a 207 | * path to a binary that will provide these, or you can use 208 | * RootTools.getWorkingToolbox() 209 | */ 210 | public static void fixUtil(String util, String utilPath) { 211 | getInternals().fixUtil(util, utilPath); 212 | } 213 | 214 | /** 215 | * This will check an array of binaries, determine if they exist and determine that it has 216 | * either the permissions 755, 775, or 777. If an applet is not setup correctly it will try and 217 | * fix it. (This is for Busybox applets or Toolbox applets) 218 | * 219 | * @param utils Name of the utility to check. 220 | * @return boolean to indicate whether the operation completed. Note that this is not indicative 221 | * of whether the problem was fixed, just that the method did not encounter any 222 | * exceptions. 223 | * @throws Exception if the operation cannot be completed. 224 | */ 225 | public static boolean fixUtils(String[] utils) throws Exception { 226 | return getInternals().fixUtils(utils); 227 | } 228 | 229 | /** 230 | * @param binaryName String that represent the binary to find. 231 | * @param singlePath boolean that represents whether to return a single path or multiple. 232 | * 233 | * @return List containing the paths the binary was found at. 234 | */ 235 | public static List findBinary(String binaryName, boolean singlePath) { 236 | return RootShell.findBinary(binaryName, singlePath); 237 | } 238 | 239 | /** 240 | * @param path String that represents the path to the Busybox binary you want to retrieve the version of. 241 | * @return BusyBox version is found, "" if not found. 242 | */ 243 | public static String getBusyBoxVersion(String path) { 244 | return getInternals().getBusyBoxVersion(path); 245 | } 246 | 247 | /** 248 | * @return BusyBox version is found, "" if not found. 249 | */ 250 | public static String getBusyBoxVersion() { 251 | return RootTools.getBusyBoxVersion(""); 252 | } 253 | 254 | /** 255 | * This will return an List of Strings. Each string represents an applet available from BusyBox. 256 | *

257 | * 258 | * @return null If we cannot return the list of applets. 259 | */ 260 | public static List getBusyBoxApplets() throws Exception { 261 | return RootTools.getBusyBoxApplets(""); 262 | } 263 | 264 | /** 265 | * This will return an List of Strings. Each string represents an applet available from BusyBox. 266 | *

267 | * 268 | * @param path Path to the busybox binary that you want the list of applets from. 269 | * @return null If we cannot return the list of applets. 270 | */ 271 | public static List getBusyBoxApplets(String path) throws Exception { 272 | return getInternals().getBusyBoxApplets(path); 273 | } 274 | 275 | /** 276 | * This will open or return, if one is already open, a custom shell, you are responsible for managing the shell, reading the output 277 | * and for closing the shell when you are done using it. 278 | * 279 | * @param shellPath a String to Indicate the path to the shell that you want to open. 280 | * @param timeout an int to Indicate the length of time before giving up on opening a shell. 281 | * @throws TimeoutException 282 | * @throws com.stericson.RootShell.exceptions.RootDeniedException 283 | * @throws IOException 284 | */ 285 | public static Shell getCustomShell(String shellPath, int timeout) throws IOException, TimeoutException, RootDeniedException { 286 | return RootShell.getCustomShell(shellPath, timeout); 287 | } 288 | 289 | /** 290 | * This will open or return, if one is already open, a custom shell, you are responsible for managing the shell, reading the output 291 | * and for closing the shell when you are done using it. 292 | * 293 | * @param shellPath a String to Indicate the path to the shell that you want to open. 294 | * @throws TimeoutException 295 | * @throws com.stericson.RootShell.exceptions.RootDeniedException 296 | * @throws IOException 297 | */ 298 | public static Shell getCustomShell(String shellPath) throws IOException, TimeoutException, RootDeniedException { 299 | return RootTools.getCustomShell(shellPath, 10000); 300 | } 301 | 302 | /** 303 | * @param file String that represent the file, including the full path to the file and its name. 304 | * @return An instance of the class permissions from which you can get the permissions of the 305 | * file or if the file could not be found or permissions couldn't be determined then 306 | * permissions will be null. 307 | */ 308 | public static Permissions getFilePermissionsSymlinks(String file) { 309 | return getInternals().getFilePermissionsSymlinks(file); 310 | } 311 | 312 | /** 313 | * This method will return the inode number of a file. This method is dependent on having a version of 314 | * ls that supports the -i parameter. 315 | * 316 | * @param file path to the file that you wish to return the inode number 317 | * @return String The inode number for this file or "" if the inode number could not be found. 318 | */ 319 | public static String getInode(String file) { 320 | return getInternals().getInode(file); 321 | } 322 | 323 | /** 324 | * This will return an ArrayList of the class Mount. The class mount contains the following 325 | * property's: device mountPoint type flags 326 | *

327 | * These will provide you with any information you need to work with the mount points. 328 | * 329 | * @return ArrayList an ArrayList of the class Mount. 330 | * @throws Exception if we cannot return the mount points. 331 | */ 332 | public static ArrayList getMounts() throws Exception { 333 | return getInternals().getMounts(); 334 | } 335 | 336 | /** 337 | * This will tell you how the specified mount is mounted. rw, ro, etc... 338 | *

339 | * 340 | * @param path The mount you want to check 341 | * @return String What the mount is mounted as. 342 | * @throws Exception if we cannot determine how the mount is mounted. 343 | */ 344 | public static String getMountedAs(String path) throws Exception { 345 | return getInternals().getMountedAs(path); 346 | } 347 | 348 | /** 349 | * This will return the environment variable PATH 350 | * 351 | * @return List A List of Strings representing the environment variable $PATH 352 | */ 353 | public static List getPath() { 354 | return Arrays.asList(System.getenv("PATH").split(":")); 355 | } 356 | 357 | /** 358 | * This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output 359 | * and for closing the shell when you are done using it. 360 | * 361 | * @param root a boolean to Indicate whether or not you want to open a root shell or a standard shell 362 | * @param timeout an int to Indicate the length of time to wait before giving up on opening a shell. 363 | * @param shellContext the context to execute the shell with 364 | * @param retry a int to indicate how many times the ROOT shell should try to open with root priviliges... 365 | * @throws TimeoutException 366 | * @throws com.stericson.RootShell.exceptions.RootDeniedException 367 | * @throws IOException 368 | */ 369 | public static Shell getShell(boolean root, int timeout, Shell.ShellContext shellContext, int retry) throws IOException, TimeoutException, RootDeniedException { 370 | return RootShell.getShell(root, timeout, shellContext, retry); 371 | } 372 | 373 | /** 374 | * This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output 375 | * and for closing the shell when you are done using it. 376 | * 377 | * @param root a boolean to Indicate whether or not you want to open a root shell or a standard shell 378 | * @param timeout an int to Indicate the length of time to wait before giving up on opening a shell. 379 | * @param shellContext the context to execute the shell with 380 | * @throws TimeoutException 381 | * @throws com.stericson.RootShell.exceptions.RootDeniedException 382 | * @throws IOException 383 | */ 384 | public static Shell getShell(boolean root, int timeout, Shell.ShellContext shellContext) throws IOException, TimeoutException, RootDeniedException { 385 | return getShell(root, timeout, shellContext, 3); 386 | } 387 | 388 | /** 389 | * This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output 390 | * and for closing the shell when you are done using it. 391 | * 392 | * @param root a boolean to Indicate whether or not you want to open a root shell or a standard shell 393 | * @param shellContext the context to execute the shell with 394 | * @throws TimeoutException 395 | * @throws com.stericson.RootShell.exceptions.RootDeniedException 396 | * @throws IOException 397 | */ 398 | public static Shell getShell(boolean root, Shell.ShellContext shellContext) throws IOException, TimeoutException, RootDeniedException { 399 | return getShell(root, 0, shellContext, 3); 400 | } 401 | 402 | /** 403 | * This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output 404 | * and for closing the shell when you are done using it. 405 | * 406 | * @param root a boolean to Indicate whether or not you want to open a root shell or a standard shell 407 | * @param timeout an int to Indicate the length of time to wait before giving up on opening a shell. 408 | * @throws TimeoutException 409 | * @throws com.stericson.RootShell.exceptions.RootDeniedException 410 | * @throws IOException 411 | */ 412 | public static Shell getShell(boolean root, int timeout) throws IOException, TimeoutException, RootDeniedException { 413 | return getShell(root, timeout, Shell.defaultContext, 3); 414 | } 415 | 416 | /** 417 | * This will open or return, if one is already open, a shell, you are responsible for managing the shell, reading the output 418 | * and for closing the shell when you are done using it. 419 | * 420 | * @param root a boolean to Indicate whether or not you want to open a root shell or a standard shell 421 | * @throws TimeoutException 422 | * @throws com.stericson.RootShell.exceptions.RootDeniedException 423 | * @throws IOException 424 | */ 425 | public static Shell getShell(boolean root) throws IOException, TimeoutException, RootDeniedException { 426 | return RootTools.getShell(root, 0); 427 | } 428 | 429 | /** 430 | * Get the space for a desired partition. 431 | * 432 | * @param path The partition to find the space for. 433 | * @return the amount if space found within the desired partition. If the space was not found 434 | * then the value is -1 435 | * @throws TimeoutException 436 | */ 437 | public static long getSpace(String path) { 438 | return getInternals().getSpace(path); 439 | } 440 | 441 | /** 442 | * This will return a String that represent the symlink for a specified file. 443 | *

444 | * 445 | * @param file path to the file to get the Symlink for. (must have absolute path) 446 | * @return String a String that represent the symlink for a specified file or an 447 | * empty string if no symlink exists. 448 | */ 449 | public static String getSymlink(String file) { 450 | return getInternals().getSymlink(file); 451 | } 452 | 453 | /** 454 | * This will return an ArrayList of the class Symlink. The class Symlink contains the following 455 | * property's: path SymplinkPath 456 | *

457 | * These will provide you with any Symlinks in the given path. 458 | * 459 | * @param path path to search for Symlinks. 460 | * @return ArrayList an ArrayList of the class Symlink. 461 | * @throws Exception if we cannot return the Symlinks. 462 | */ 463 | public static ArrayList getSymlinks(String path) throws Exception { 464 | return getInternals().getSymlinks(path); 465 | } 466 | 467 | /** 468 | * This will return to you a string to be used in your shell commands which will represent the 469 | * valid working toolbox with correct permissions. For instance, if Busybox is available it will 470 | * return "busybox", if busybox is not available but toolbox is then it will return "toolbox" 471 | * 472 | * @return String that indicates the available toolbox to use for accessing applets. 473 | */ 474 | public static String getWorkingToolbox() { 475 | return getInternals().getWorkingToolbox(); 476 | } 477 | 478 | /** 479 | * Checks if there is enough Space on SDCard 480 | * 481 | * @param updateSize size to Check (long) 482 | * @return true if the Update will fit on SDCard, false if not enough 483 | * space on SDCard. Will also return false, if the SDCard is not mounted as 484 | * read/write 485 | */ 486 | public static boolean hasEnoughSpaceOnSdCard(long updateSize) { 487 | return getInternals().hasEnoughSpaceOnSdCard(updateSize); 488 | } 489 | 490 | /** 491 | * Checks whether the toolbox or busybox binary contains a specific util 492 | * 493 | * @param util 494 | * @param box Should contain "toolbox" or "busybox" 495 | * @return true if it contains this util 496 | */ 497 | public static boolean hasUtil(final String util, final String box) { 498 | //TODO Convert this to use the new shell. 499 | return getInternals().hasUtil(util, box); 500 | } 501 | 502 | /** 503 | * This method can be used to unpack a binary from the raw resources folder and store it in 504 | * /data/data/app.package/files/ This is typically useful if you provide your own C- or 505 | * C++-based binary. This binary can then be executed using sendShell() and its full path. 506 | * 507 | * @param context the current activity's Context 508 | * @param sourceId resource id; typically R.raw.id 509 | * @param destName destination file name; appended to /data/data/app.package/files/ 510 | * @param mode chmod value for this file 511 | * @return a boolean which indicates whether or not we were able to create the new 512 | * file. 513 | */ 514 | public static boolean installBinary(Context context, int sourceId, String destName, String mode) { 515 | return getInternals().installBinary(context, sourceId, destName, mode); 516 | } 517 | 518 | /** 519 | * This method can be used to unpack a binary from the raw resources folder and store it in 520 | * /data/data/app.package/files/ This is typically useful if you provide your own C- or 521 | * C++-based binary. This binary can then be executed using sendShell() and its full path. 522 | * 523 | * @param context the current activity's Context 524 | * @param sourceId resource id; typically R.raw.id 525 | * @param binaryName destination file name; appended to /data/data/app.package/files/ 526 | * @return a boolean which indicates whether or not we were able to create the new 527 | * file. 528 | */ 529 | public static boolean installBinary(Context context, int sourceId, String binaryName) { 530 | return installBinary(context, sourceId, binaryName, "700"); 531 | } 532 | 533 | /** 534 | * This method checks whether a binary is installed. 535 | * 536 | * @param context the current activity's Context 537 | * @param binaryName binary file name; appended to /data/data/app.package/files/ 538 | * @return a boolean which indicates whether or not 539 | * the binary already exists. 540 | */ 541 | public static boolean hasBinary(Context context, String binaryName) { 542 | return getInternals().isBinaryAvailable(context, binaryName); 543 | } 544 | 545 | /** 546 | * This will let you know if an applet is available from BusyBox 547 | *

548 | * 549 | * @param applet The applet to check for. 550 | * @param path Path to the busybox binary that you want to check. (do not include binary name) 551 | * @return true if applet is available, false otherwise. 552 | */ 553 | public static boolean isAppletAvailable(String applet, String path) { 554 | return getInternals().isAppletAvailable(applet, path); 555 | } 556 | 557 | /** 558 | * This will let you know if an applet is available from BusyBox 559 | *

560 | * 561 | * @param applet The applet to check for. 562 | * @return true if applet is available, false otherwise. 563 | */ 564 | public static boolean isAppletAvailable(String applet) { 565 | return RootTools.isAppletAvailable(applet, ""); 566 | } 567 | /** 568 | * @return true if your app has been given root access. 569 | * @throws TimeoutException if this operation times out. (cannot determine if access is given) 570 | */ 571 | public static boolean isAccessGiven() { 572 | return RootShell.isAccessGiven(0, 3); 573 | } 574 | 575 | /** 576 | * Control how many time of retries should request 577 | * 578 | * @param timeout The timeout 579 | * @param retries The number of retries 580 | * 581 | * @return true if your app has been given root access. 582 | * @throws TimeoutException if this operation times out. (cannot determine if access is given) 583 | */ 584 | public static boolean isAccessGiven(int timeout, int retries) { 585 | return RootShell.isAccessGiven(timeout, retries); 586 | } 587 | 588 | /** 589 | * @return true if BusyBox was found. 590 | */ 591 | public static boolean isBusyboxAvailable() { 592 | return RootShell.isBusyboxAvailable(); 593 | } 594 | 595 | public static boolean isNativeToolsReady(int nativeToolsId, Context context) { 596 | return getInternals().isNativeToolsReady(nativeToolsId, context); 597 | } 598 | 599 | /** 600 | * This method can be used to to check if a process is running 601 | * 602 | * @param processName name of process to check 603 | * @return true if process was found 604 | * @throws TimeoutException (Could not determine if the process is running) 605 | */ 606 | public static boolean isProcessRunning(final String processName) { 607 | //TODO convert to new shell 608 | return getInternals().isProcessRunning(processName); 609 | } 610 | 611 | /** 612 | * @return true if su was found. 613 | */ 614 | public static boolean isRootAvailable() { 615 | return RootShell.isRootAvailable(); 616 | } 617 | 618 | /** 619 | * This method can be used to kill a running process 620 | * 621 | * @param processName name of process to kill 622 | * @return true if process was found and killed successfully 623 | */ 624 | public static boolean killProcess(final String processName) { 625 | //TODO convert to new shell 626 | return getInternals().killProcess(processName); 627 | } 628 | 629 | /** 630 | * This will launch the Android market looking for BusyBox 631 | * 632 | * @param activity pass in your Activity 633 | */ 634 | public static void offerBusyBox(Activity activity) { 635 | getInternals().offerBusyBox(activity); 636 | } 637 | 638 | /** 639 | * This will launch the Android market looking for BusyBox, but will return the intent fired and 640 | * starts the activity with startActivityForResult 641 | * 642 | * @param activity pass in your Activity 643 | * @param requestCode pass in the request code 644 | * @return intent fired 645 | */ 646 | public static Intent offerBusyBox(Activity activity, int requestCode) { 647 | return getInternals().offerBusyBox(activity, requestCode); 648 | } 649 | 650 | /** 651 | * This will launch the Android market looking for SuperUser 652 | * 653 | * @param activity pass in your Activity 654 | */ 655 | public static void offerSuperUser(Activity activity) { 656 | getInternals().offerSuperUser(activity); 657 | } 658 | 659 | /** 660 | * This will launch the Android market looking for SuperUser, but will return the intent fired 661 | * and starts the activity with startActivityForResult 662 | * 663 | * @param activity pass in your Activity 664 | * @param requestCode pass in the request code 665 | * @return intent fired 666 | */ 667 | public static Intent offerSuperUser(Activity activity, int requestCode) { 668 | return getInternals().offerSuperUser(activity, requestCode); 669 | } 670 | 671 | /** 672 | * This will take a path, which can contain the file name as well, and attempt to remount the 673 | * underlying partition. 674 | *

675 | * For example, passing in the following string: 676 | * "/system/bin/some/directory/that/really/would/never/exist" will result in /system ultimately 677 | * being remounted. However, keep in mind that the longer the path you supply, the more work 678 | * this has to do, and the slower it will run. 679 | * 680 | * @param file file path 681 | * @param mountType mount type: pass in RO (Read only) or RW (Read Write) 682 | * @return a boolean which indicates whether or not the partition has been 683 | * remounted as specified. 684 | */ 685 | public static boolean remount(String file, String mountType) { 686 | // Recieved a request, get an instance of Remounter 687 | Remounter remounter = new Remounter(); 688 | // send the request. 689 | return (remounter.remount(file, mountType)); 690 | } 691 | 692 | /** 693 | * This restarts only Android OS without rebooting the whole device. This does NOT work on all 694 | * devices. This is done by killing the main init process named zygote. Zygote is restarted 695 | * automatically by Android after killing it. 696 | * 697 | * @throws TimeoutException 698 | */ 699 | public static void restartAndroid() { 700 | RootTools.log("Restart Android"); 701 | killProcess("zygote"); 702 | } 703 | 704 | /** 705 | * Executes binary in a separated process. Before using this method, the binary has to be 706 | * installed in /data/data/app.package/files/ using the installBinary method. 707 | * 708 | * @param context the current activity's Context 709 | * @param binaryName name of installed binary 710 | * @param parameter parameter to append to binary like "-vxf" 711 | */ 712 | public static void runBinary(Context context, String binaryName, String parameter) { 713 | Runner runner = new Runner(context, binaryName, parameter); 714 | runner.start(); 715 | } 716 | 717 | /** 718 | * Executes a given command with root access or without depending on the value of the boolean passed. 719 | * This will also start a root shell or a standard shell without you having to open it specifically. 720 | *

721 | * You will still need to close the shell after you are done using the shell. 722 | * 723 | * @param shell The shell to execute the command on, this can be a root shell or a standard shell. 724 | * @param command The command to execute in the shell 725 | * 726 | * @throws IOException 727 | */ 728 | public static void runShellCommand(Shell shell, Command command) throws IOException { 729 | shell.add(command); 730 | } 731 | 732 | /** 733 | * This method allows you to output debug messages only when debugging is on. This will allow 734 | * you to add a debug option to your app, which by default can be left off for performance. 735 | * However, when you need debugging information, a simple switch can enable it and provide you 736 | * with detailed logging. 737 | *

738 | * This method handles whether or not to log the information you pass it depending whether or 739 | * not RootTools.debugMode is on. So you can use this and not have to worry about handling it 740 | * yourself. 741 | * 742 | * @param msg The message to output. 743 | */ 744 | public static void log(String msg) { 745 | log(null, msg, 3, null); 746 | } 747 | 748 | /** 749 | * This method allows you to output debug messages only when debugging is on. This will allow 750 | * you to add a debug option to your app, which by default can be left off for performance. 751 | * However, when you need debugging information, a simple switch can enable it and provide you 752 | * with detailed logging. 753 | *

754 | * This method handles whether or not to log the information you pass it depending whether or 755 | * not RootTools.debugMode is on. So you can use this and not have to worry about handling it 756 | * yourself. 757 | * 758 | * @param TAG Optional parameter to define the tag that the Log will use. 759 | * @param msg The message to output. 760 | */ 761 | public static void log(String TAG, String msg) { 762 | log(TAG, msg, 3, null); 763 | } 764 | 765 | /** 766 | * This method allows you to output debug messages only when debugging is on. This will allow 767 | * you to add a debug option to your app, which by default can be left off for performance. 768 | * However, when you need debugging information, a simple switch can enable it and provide you 769 | * with detailed logging. 770 | *

771 | * This method handles whether or not to log the information you pass it depending whether or 772 | * not RootTools.debugMode is on. So you can use this and not have to worry about handling it 773 | * yourself. 774 | * 775 | * @param msg The message to output. 776 | * @param type The type of log, 1 for verbose, 2 for error, 3 for debug 777 | * @param e The exception that was thrown (Needed for errors) 778 | */ 779 | public static void log(String msg, int type, Exception e) { 780 | log(null, msg, type, e); 781 | } 782 | 783 | /** 784 | * This method allows you to check whether logging is enabled. 785 | * Yes, it has a goofy name, but that's to keep it as short as possible. 786 | * After all writing logging calls should be painless. 787 | * This method exists to save Android going through the various Java layers 788 | * that are traversed any time a string is created (i.e. what you are logging) 789 | *

790 | * Example usage: 791 | * if(islog) { 792 | * StrinbBuilder sb = new StringBuilder(); 793 | * // ... 794 | * // build string 795 | * // ... 796 | * log(sb.toString()); 797 | * } 798 | * 799 | * @return true if logging is enabled 800 | */ 801 | public static boolean islog() { 802 | return debugMode; 803 | } 804 | 805 | /** 806 | * This method allows you to output debug messages only when debugging is on. This will allow 807 | * you to add a debug option to your app, which by default can be left off for performance. 808 | * However, when you need debugging information, a simple switch can enable it and provide you 809 | * with detailed logging. 810 | *

811 | * This method handles whether or not to log the information you pass it depending whether or 812 | * not RootTools.debugMode is on. So you can use this and not have to worry about handling it 813 | * yourself. 814 | * 815 | * @param TAG Optional parameter to define the tag that the Log will use. 816 | * @param msg The message to output. 817 | * @param type The type of log, 1 for verbose, 2 for error, 3 for debug 818 | * @param e The exception that was thrown (Needed for errors) 819 | */ 820 | public static void log(String TAG, String msg, int type, Exception e) { 821 | if (msg != null && !msg.equals("")) { 822 | if (debugMode) { 823 | if (TAG == null) { 824 | TAG = Constants.TAG; 825 | } 826 | 827 | switch (type) { 828 | case 1: 829 | Log.v(TAG, msg); 830 | break; 831 | case 2: 832 | Log.e(TAG, msg, e); 833 | break; 834 | case 3: 835 | Log.d(TAG, msg); 836 | break; 837 | } 838 | } 839 | } 840 | } 841 | } 842 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/SanityCheckRootTools.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the RootTools Project: http://code.google.com/p/RootTools/ 3 | * 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks 5 | * 6 | * This code is dual-licensed under the terms of the Apache License Version 2.0 and 7 | * the terms of the General Public License (GPL) Version 2. 8 | * You may use this code according to either of these licenses as is most appropriate 9 | * for your project on a case-by-case basis. 10 | * 11 | * The terms of each license can be found in the root directory of this project's repository as well as at: 12 | * 13 | * * http://www.apache.org/licenses/LICENSE-2.0 14 | * * http://www.gnu.org/licenses/gpl-2.0.txt 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under these Licenses is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See each License for the specific language governing permissions and 20 | * limitations under that License. 21 | */ 22 | 23 | package com.stericson.RootTools; 24 | 25 | import android.app.Activity; 26 | import android.app.ProgressDialog; 27 | import android.content.Context; 28 | import android.os.Bundle; 29 | import android.os.Handler; 30 | import android.os.Message; 31 | import android.os.StrictMode; 32 | import android.widget.ScrollView; 33 | import android.widget.TextView; 34 | 35 | import com.stericson.RootShell.exceptions.RootDeniedException; 36 | import com.stericson.RootShell.execution.Command; 37 | import com.stericson.RootShell.execution.Shell; 38 | import com.stericson.RootTools.containers.Permissions; 39 | 40 | import java.io.IOException; 41 | import java.util.List; 42 | import java.util.concurrent.TimeoutException; 43 | 44 | public class SanityCheckRootTools extends Activity { 45 | private ScrollView mScrollView; 46 | private TextView mTextView; 47 | private ProgressDialog mPDialog; 48 | 49 | @Override 50 | public void onCreate(Bundle savedInstanceState) { 51 | super.onCreate(savedInstanceState); 52 | 53 | StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder() 54 | .detectDiskReads() 55 | .detectDiskWrites() 56 | .detectNetwork() // or .detectAll() for all detectable problems 57 | .penaltyLog() 58 | .build()); 59 | StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder() 60 | .detectLeakedSqlLiteObjects() 61 | .detectLeakedClosableObjects() 62 | .penaltyLog() 63 | .penaltyDeath() 64 | .build()); 65 | 66 | RootTools.debugMode = true; 67 | 68 | mTextView = new TextView(this); 69 | mTextView.setText(""); 70 | mScrollView = new ScrollView(this); 71 | mScrollView.addView(mTextView); 72 | setContentView(mScrollView); 73 | 74 | print("SanityCheckRootTools \n\n"); 75 | 76 | if (RootTools.isRootAvailable()) { 77 | print("Root found.\n"); 78 | } else { 79 | print("Root not found"); 80 | } 81 | 82 | try { 83 | Shell.startRootShell(); 84 | } catch (IOException e2) { 85 | // TODO Auto-generated catch block 86 | e2.printStackTrace(); 87 | } catch (TimeoutException e) { 88 | print("[ TIMEOUT EXCEPTION! ]\n"); 89 | e.printStackTrace(); 90 | } catch (RootDeniedException e) { 91 | print("[ ROOT DENIED EXCEPTION! ]\n"); 92 | e.printStackTrace(); 93 | } 94 | 95 | try { 96 | if (!RootTools.isAccessGiven()) { 97 | print("ERROR: No root access to this device.\n"); 98 | return; 99 | } 100 | } catch (Exception e) { 101 | print("ERROR: could not determine root access to this device.\n"); 102 | return; 103 | } 104 | 105 | // Display infinite progress bar 106 | mPDialog = new ProgressDialog(this); 107 | mPDialog.setCancelable(false); 108 | mPDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); 109 | 110 | new SanityCheckThread(this, new TestHandler()).start(); 111 | } 112 | 113 | protected void print(CharSequence text) { 114 | mTextView.append(text); 115 | mScrollView.post(new Runnable() { 116 | public void run() { 117 | mScrollView.fullScroll(ScrollView.FOCUS_DOWN); 118 | } 119 | }); 120 | } 121 | 122 | // Run our long-running tests in their separate thread so as to 123 | // not interfere with proper rendering. 124 | private class SanityCheckThread extends Thread { 125 | private Handler mHandler; 126 | 127 | public SanityCheckThread(Context context, Handler handler) { 128 | mHandler = handler; 129 | } 130 | 131 | public void run() { 132 | visualUpdate(TestHandler.ACTION_SHOW, null); 133 | 134 | // First test: Install a binary file for future use 135 | // if it wasn't already installed. 136 | /* 137 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Installing binary if needed"); 138 | if(false == RootTools.installBinary(mContext, R.raw.nes, "nes_binary")) { 139 | visualUpdate(TestHandler.ACTION_HIDE, "ERROR: Failed to install binary. Please see log file."); 140 | return; 141 | } 142 | */ 143 | 144 | boolean result; 145 | 146 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getPath"); 147 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ getPath ]\n"); 148 | 149 | try { 150 | List paths = RootTools.getPath(); 151 | 152 | for (String path : paths) { 153 | visualUpdate(TestHandler.ACTION_DISPLAY, path + " k\n\n"); 154 | } 155 | 156 | } catch (Exception e) { 157 | e.printStackTrace(); 158 | } 159 | 160 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing A ton of commands"); 161 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Ton of Commands ]\n"); 162 | 163 | for (int i = 0; i < 100; i++) { 164 | RootTools.exists("/system/xbin/busybox"); 165 | } 166 | 167 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Find Binary"); 168 | result = RootTools.isRootAvailable(); 169 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Root ]\n"); 170 | visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n"); 171 | 172 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing file exists"); 173 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Exists() ]\n"); 174 | visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.exists("/system/sbin/[") + " k\n\n"); 175 | 176 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Is Access Given"); 177 | result = RootTools.isAccessGiven(); 178 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking for Access to Root ]\n"); 179 | visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n"); 180 | 181 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing Remount"); 182 | result = RootTools.remount("/system", "rw"); 183 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Remounting System as RW ]\n"); 184 | visualUpdate(TestHandler.ACTION_DISPLAY, result + " k\n\n"); 185 | 186 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing CheckUtil"); 187 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking busybox is setup ]\n"); 188 | visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.checkUtil("busybox") + " k\n\n"); 189 | 190 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getBusyBoxVersion"); 191 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking busybox version ]\n"); 192 | visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.getBusyBoxVersion("/system/xbin/") + " k\n\n"); 193 | 194 | try { 195 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing fixUtils"); 196 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Utils ]\n"); 197 | visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.fixUtils(new String[]{"ls", "rm", "ln", "dd", "chmod", "mount"}) + " k\n\n"); 198 | } catch (Exception e2) { 199 | // TODO Auto-generated catch block 200 | e2.printStackTrace(); 201 | } 202 | 203 | try { 204 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getSymlink"); 205 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking [[ for symlink ]\n"); 206 | visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.getSymlink("/system/bin/[[") + " k\n\n"); 207 | } catch (Exception e2) { 208 | // TODO Auto-generated catch block 209 | e2.printStackTrace(); 210 | } 211 | 212 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getInode"); 213 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking Inodes ]\n"); 214 | visualUpdate(TestHandler.ACTION_DISPLAY, RootTools.getInode("/system/bin/busybox") + " k\n\n"); 215 | 216 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing GetBusyBoxapplets"); 217 | try { 218 | 219 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Getting all available Busybox applets ]\n"); 220 | for (String applet : RootTools.getBusyBoxApplets("/data/data/stericson.busybox/files/bb/busybox")) { 221 | visualUpdate(TestHandler.ACTION_DISPLAY, applet + " k\n\n"); 222 | } 223 | 224 | } catch (Exception e1) { 225 | // TODO Auto-generated catch block 226 | e1.printStackTrace(); 227 | } 228 | 229 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing GetBusyBox version in a special directory!"); 230 | try { 231 | 232 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Testing GetBusyBox version in a special directory! ]\n"); 233 | String v = RootTools.getBusyBoxVersion("/data/data/stericson.busybox/files/bb/"); 234 | 235 | visualUpdate(TestHandler.ACTION_DISPLAY, v + " k\n\n"); 236 | 237 | } catch (Exception e1) { 238 | // TODO Auto-generated catch block 239 | e1.printStackTrace(); 240 | } 241 | 242 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing getFilePermissionsSymlinks"); 243 | Permissions permissions = RootTools.getFilePermissionsSymlinks("/system/xbin/busybox"); 244 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking busybox permissions and symlink ]\n"); 245 | 246 | if (permissions != null) { 247 | visualUpdate(TestHandler.ACTION_DISPLAY, "Symlink: " + permissions.getSymlink() + " k\n\n"); 248 | visualUpdate(TestHandler.ACTION_DISPLAY, "Group Permissions: " + permissions.getGroupPermissions() + " k\n\n"); 249 | visualUpdate(TestHandler.ACTION_DISPLAY, "Owner Permissions: " + permissions.getOtherPermissions() + " k\n\n"); 250 | visualUpdate(TestHandler.ACTION_DISPLAY, "Permissions: " + permissions.getPermissions() + " k\n\n"); 251 | visualUpdate(TestHandler.ACTION_DISPLAY, "Type: " + permissions.getType() + " k\n\n"); 252 | visualUpdate(TestHandler.ACTION_DISPLAY, "User Permissions: " + permissions.getUserPermissions() + " k\n\n"); 253 | } else { 254 | visualUpdate(TestHandler.ACTION_DISPLAY, "Permissions == null k\n\n"); 255 | } 256 | 257 | Shell shell; 258 | 259 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing output capture"); 260 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ busybox ash --help ]\n"); 261 | 262 | try { 263 | shell = RootTools.getShell(true); 264 | Command cmd = new Command( 265 | 0, 266 | "busybox ash --help") { 267 | 268 | @Override 269 | public void commandOutput(int id, String line) { 270 | visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n"); 271 | super.commandOutput(id, line); 272 | } 273 | }; 274 | shell.add(cmd); 275 | 276 | visualUpdate(TestHandler.ACTION_PDISPLAY, "getevent - /dev/input/event0"); 277 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ getevent - /dev/input/event0 ]\n"); 278 | 279 | cmd = new Command(0, 0, "getevent /dev/input/event0") { 280 | @Override 281 | public void commandOutput(int id, String line) { 282 | visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n"); 283 | super.commandOutput(id, line); 284 | } 285 | 286 | }; 287 | shell.add(cmd); 288 | 289 | } catch (Exception e) { 290 | e.printStackTrace(); 291 | } 292 | 293 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Switching RootContext - SYSTEM_APP"); 294 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Switching Root Context - SYSTEM_APP ]\n"); 295 | 296 | try { 297 | shell = RootTools.getShell(true, Shell.ShellContext.SYSTEM_APP); 298 | Command cmd = new Command( 299 | 0, 300 | "id") { 301 | 302 | @Override 303 | public void commandOutput(int id, String line) { 304 | visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n"); 305 | super.commandOutput(id, line); 306 | } 307 | }; 308 | shell.add(cmd); 309 | 310 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing PM"); 311 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Testing pm list packages -d ]\n"); 312 | 313 | cmd = new Command( 314 | 0, 315 | "sh /system/bin/pm list packages -d") { 316 | 317 | @Override 318 | public void commandOutput(int id, String line) { 319 | visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n"); 320 | super.commandOutput(id, line); 321 | } 322 | }; 323 | shell.add(cmd); 324 | 325 | } catch (Exception e) { 326 | e.printStackTrace(); 327 | } 328 | 329 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Switching RootContext - UNTRUSTED"); 330 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Switching Root Context - UNTRUSTED ]\n"); 331 | 332 | try { 333 | shell = RootTools.getShell(true, Shell.ShellContext.UNTRUSTED_APP); 334 | Command cmd = new Command( 335 | 0, 336 | "id") { 337 | 338 | @Override 339 | public void commandOutput(int id, String line) { 340 | visualUpdate(TestHandler.ACTION_DISPLAY, line + "\n"); 341 | super.commandOutput(id, line); 342 | } 343 | }; 344 | shell.add(cmd); 345 | 346 | } catch (Exception e) { 347 | e.printStackTrace(); 348 | } 349 | 350 | visualUpdate(TestHandler.ACTION_PDISPLAY, "Testing df"); 351 | long spaceValue = RootTools.getSpace("/data"); 352 | visualUpdate(TestHandler.ACTION_DISPLAY, "[ Checking /data partition size]\n"); 353 | visualUpdate(TestHandler.ACTION_DISPLAY, spaceValue + "k\n\n"); 354 | 355 | try { 356 | shell = RootTools.getShell(true); 357 | 358 | Command cmd = new Command(42, false, "echo done") { 359 | 360 | boolean _catch = false; 361 | 362 | @Override 363 | public void commandOutput(int id, String line) { 364 | if (_catch) { 365 | RootTools.log("CAUGHT!!!"); 366 | } 367 | 368 | super.commandOutput(id, line); 369 | 370 | } 371 | 372 | @Override 373 | public void commandTerminated(int id, String reason) { 374 | synchronized (SanityCheckRootTools.this) { 375 | 376 | _catch = true; 377 | visualUpdate(TestHandler.ACTION_PDISPLAY, "All tests complete."); 378 | visualUpdate(TestHandler.ACTION_HIDE, null); 379 | 380 | try { 381 | RootTools.closeAllShells(); 382 | } catch (IOException e) { 383 | // TODO Auto-generated catch block 384 | e.printStackTrace(); 385 | } 386 | 387 | } 388 | } 389 | 390 | @Override 391 | public void commandCompleted(int id, int exitCode) { 392 | synchronized (SanityCheckRootTools.this) { 393 | _catch = true; 394 | 395 | visualUpdate(TestHandler.ACTION_PDISPLAY, "All tests complete."); 396 | visualUpdate(TestHandler.ACTION_HIDE, null); 397 | 398 | try { 399 | RootTools.closeAllShells(); 400 | } catch (IOException e) { 401 | // TODO Auto-generated catch block 402 | e.printStackTrace(); 403 | } 404 | 405 | } 406 | } 407 | }; 408 | 409 | shell.add(cmd); 410 | 411 | } catch (Exception e) { 412 | e.printStackTrace(); 413 | } 414 | 415 | } 416 | 417 | private void visualUpdate(int action, String text) { 418 | Message msg = mHandler.obtainMessage(); 419 | Bundle bundle = new Bundle(); 420 | bundle.putInt(TestHandler.ACTION, action); 421 | bundle.putString(TestHandler.TEXT, text); 422 | msg.setData(bundle); 423 | mHandler.sendMessage(msg); 424 | } 425 | } 426 | 427 | private class TestHandler extends Handler { 428 | static final public String ACTION = "action"; 429 | static final public int ACTION_SHOW = 0x01; 430 | static final public int ACTION_HIDE = 0x02; 431 | static final public int ACTION_DISPLAY = 0x03; 432 | static final public int ACTION_PDISPLAY = 0x04; 433 | static final public String TEXT = "text"; 434 | 435 | public void handleMessage(Message msg) { 436 | int action = msg.getData().getInt(ACTION); 437 | String text = msg.getData().getString(TEXT); 438 | 439 | switch (action) { 440 | case ACTION_SHOW: 441 | mPDialog.show(); 442 | mPDialog.setMessage("Running Root Library Tests..."); 443 | break; 444 | case ACTION_HIDE: 445 | if (null != text) { 446 | print(text); 447 | } 448 | mPDialog.hide(); 449 | break; 450 | case ACTION_DISPLAY: 451 | print(text); 452 | break; 453 | case ACTION_PDISPLAY: 454 | mPDialog.setMessage(text); 455 | break; 456 | } 457 | } 458 | } 459 | } 460 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/containers/Mount.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the RootTools Project: http://code.google.com/p/RootTools/ 3 | * 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks 5 | * 6 | * This code is dual-licensed under the terms of the Apache License Version 2.0 and 7 | * the terms of the General Public License (GPL) Version 2. 8 | * You may use this code according to either of these licenses as is most appropriate 9 | * for your project on a case-by-case basis. 10 | * 11 | * The terms of each license can be found in the root directory of this project's repository as well as at: 12 | * 13 | * * http://www.apache.org/licenses/LICENSE-2.0 14 | * * http://www.gnu.org/licenses/gpl-2.0.txt 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under these Licenses is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See each License for the specific language governing permissions and 20 | * limitations under that License. 21 | */ 22 | 23 | package com.stericson.RootTools.containers; 24 | 25 | import java.io.File; 26 | import java.util.Arrays; 27 | import java.util.LinkedHashSet; 28 | import java.util.Set; 29 | 30 | public class Mount 31 | { 32 | final File mDevice; 33 | final File mMountPoint; 34 | final String mType; 35 | final Set mFlags; 36 | 37 | public Mount(File device, File path, String type, String flagsStr) 38 | { 39 | mDevice = device; 40 | mMountPoint = path; 41 | mType = type; 42 | mFlags = new LinkedHashSet(Arrays.asList(flagsStr.split(","))); 43 | } 44 | 45 | public File getDevice() 46 | { 47 | return mDevice; 48 | } 49 | 50 | public File getMountPoint() 51 | { 52 | return mMountPoint; 53 | } 54 | 55 | public String getType() 56 | { 57 | return mType; 58 | } 59 | 60 | public Set getFlags() 61 | { 62 | return mFlags; 63 | } 64 | 65 | @Override 66 | public String toString() 67 | { 68 | return String.format("%s on %s type %s %s", mDevice, mMountPoint, mType, mFlags); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/containers/Permissions.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the RootTools Project: http://code.google.com/p/RootTools/ 3 | * 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks 5 | * 6 | * This code is dual-licensed under the terms of the Apache License Version 2.0 and 7 | * the terms of the General Public License (GPL) Version 2. 8 | * You may use this code according to either of these licenses as is most appropriate 9 | * for your project on a case-by-case basis. 10 | * 11 | * The terms of each license can be found in the root directory of this project's repository as well as at: 12 | * 13 | * * http://www.apache.org/licenses/LICENSE-2.0 14 | * * http://www.gnu.org/licenses/gpl-2.0.txt 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under these Licenses is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See each License for the specific language governing permissions and 20 | * limitations under that License. 21 | */ 22 | 23 | package com.stericson.RootTools.containers; 24 | 25 | public class Permissions 26 | { 27 | String type; 28 | String user; 29 | String group; 30 | String other; 31 | String symlink; 32 | int permissions; 33 | 34 | public String getSymlink() 35 | { 36 | return this.symlink; 37 | } 38 | 39 | public String getType() 40 | { 41 | return type; 42 | } 43 | 44 | public int getPermissions() 45 | { 46 | return this.permissions; 47 | } 48 | 49 | public String getUserPermissions() 50 | { 51 | return this.user; 52 | } 53 | 54 | public String getGroupPermissions() 55 | { 56 | return this.group; 57 | } 58 | 59 | public String getOtherPermissions() 60 | { 61 | return this.other; 62 | } 63 | 64 | public void setSymlink(String symlink) 65 | { 66 | this.symlink = symlink; 67 | } 68 | 69 | public void setType(String type) 70 | { 71 | this.type = type; 72 | } 73 | 74 | public void setPermissions(int permissions) 75 | { 76 | this.permissions = permissions; 77 | } 78 | 79 | public void setUserPermissions(String user) 80 | { 81 | this.user = user; 82 | } 83 | 84 | public void setGroupPermissions(String group) 85 | { 86 | this.group = group; 87 | } 88 | 89 | public void setOtherPermissions(String other) 90 | { 91 | this.other = other; 92 | } 93 | 94 | public String getUser() 95 | { 96 | return user; 97 | } 98 | 99 | public void setUser(String user) 100 | { 101 | this.user = user; 102 | } 103 | 104 | public String getGroup() 105 | { 106 | return group; 107 | } 108 | 109 | public void setGroup(String group) 110 | { 111 | this.group = group; 112 | } 113 | 114 | public String getOther() 115 | { 116 | return other; 117 | } 118 | 119 | public void setOther(String other) 120 | { 121 | this.other = other; 122 | } 123 | 124 | 125 | } 126 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/containers/Symlink.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the RootTools Project: http://code.google.com/p/RootTools/ 3 | * 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks 5 | * 6 | * This code is dual-licensed under the terms of the Apache License Version 2.0 and 7 | * the terms of the General Public License (GPL) Version 2. 8 | * You may use this code according to either of these licenses as is most appropriate 9 | * for your project on a case-by-case basis. 10 | * 11 | * The terms of each license can be found in the root directory of this project's repository as well as at: 12 | * 13 | * * http://www.apache.org/licenses/LICENSE-2.0 14 | * * http://www.gnu.org/licenses/gpl-2.0.txt 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under these Licenses is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See each License for the specific language governing permissions and 20 | * limitations under that License. 21 | */ 22 | 23 | package com.stericson.RootTools.containers; 24 | 25 | import java.io.File; 26 | 27 | public class Symlink 28 | { 29 | protected final File file; 30 | protected final File symlinkPath; 31 | 32 | public Symlink(File file, File path) 33 | { 34 | this.file = file; 35 | symlinkPath = path; 36 | } 37 | 38 | public File getFile() 39 | { 40 | return this.file; 41 | } 42 | 43 | public File getSymlinkPath() 44 | { 45 | return symlinkPath; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/internal/Installer.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the RootTools Project: http://code.google.com/p/RootTools/ 3 | * 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks 5 | * 6 | * This code is dual-licensed under the terms of the Apache License Version 2.0 and 7 | * the terms of the General Public License (GPL) Version 2. 8 | * You may use this code according to either of these licenses as is most appropriate 9 | * for your project on a case-by-case basis. 10 | * 11 | * The terms of each license can be found in the root directory of this project's repository as well as at: 12 | * 13 | * * http://www.apache.org/licenses/LICENSE-2.0 14 | * * http://www.gnu.org/licenses/gpl-2.0.txt 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under these Licenses is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See each License for the specific language governing permissions and 20 | * limitations under that License. 21 | */ 22 | 23 | package com.stericson.RootTools.internal; 24 | 25 | import java.io.File; 26 | import java.io.FileInputStream; 27 | import java.io.FileNotFoundException; 28 | import java.io.FileOutputStream; 29 | import java.io.IOException; 30 | import java.io.InputStream; 31 | import java.nio.channels.Channels; 32 | import java.nio.channels.FileChannel; 33 | import java.nio.channels.ReadableByteChannel; 34 | import java.security.DigestInputStream; 35 | import java.security.MessageDigest; 36 | import java.security.NoSuchAlgorithmException; 37 | 38 | import android.util.Log; 39 | 40 | import com.stericson.RootShell.execution.Command; 41 | import com.stericson.RootShell.execution.Shell; 42 | import com.stericson.RootTools.RootTools; 43 | 44 | import android.content.Context; 45 | 46 | class Installer 47 | { 48 | 49 | //------------- 50 | //# Installer # 51 | //------------- 52 | 53 | static final String LOG_TAG = "RootTools::Installer"; 54 | 55 | static final String BOGUS_FILE_NAME = "bogus"; 56 | 57 | Context context; 58 | String filesPath; 59 | 60 | public Installer(Context context) 61 | throws IOException 62 | { 63 | 64 | this.context = context; 65 | this.filesPath = context.getFilesDir().getCanonicalPath(); 66 | } 67 | 68 | /** 69 | * This method can be used to unpack a binary from the raw resources folder and store it in 70 | * /data/data/app.package/files/ 71 | * This is typically useful if you provide your own C- or C++-based binary. 72 | * This binary can then be executed using sendShell() and its full path. 73 | * 74 | * @param sourceId resource id; typically R.raw.id 75 | * @param destName destination file name; appended to /data/data/app.package/files/ 76 | * @param mode chmod value for this file 77 | * @return a boolean which indicates whether or not we were 78 | * able to create the new file. 79 | */ 80 | protected boolean installBinary(int sourceId, String destName, String mode) 81 | { 82 | File mf = new File(filesPath + File.separator + destName); 83 | if (!mf.exists() || 84 | !getFileSignature(mf).equals( 85 | getStreamSignature( 86 | context.getResources().openRawResource(sourceId)) 87 | )) 88 | { 89 | Log.e(LOG_TAG, "Installing a new version of binary: " + destName); 90 | // First, does our files/ directory even exist? 91 | // We cannot wait for android to lazily create it as we will soon 92 | // need it. 93 | try 94 | { 95 | FileInputStream fis = context.openFileInput(BOGUS_FILE_NAME); 96 | fis.close(); 97 | } 98 | catch (FileNotFoundException e) 99 | { 100 | FileOutputStream fos = null; 101 | try 102 | { 103 | fos = context.openFileOutput("bogus", Context.MODE_PRIVATE); 104 | fos.write("justcreatedfilesdirectory".getBytes()); 105 | } 106 | catch (Exception ex) 107 | { 108 | if (RootTools.debugMode) 109 | { 110 | Log.e(LOG_TAG, ex.toString()); 111 | } 112 | return false; 113 | } 114 | finally 115 | { 116 | if (null != fos) 117 | { 118 | try 119 | { 120 | fos.close(); 121 | context.deleteFile(BOGUS_FILE_NAME); 122 | } 123 | catch (IOException e1) 124 | { 125 | } 126 | } 127 | } 128 | } 129 | catch (IOException ex) 130 | { 131 | if (RootTools.debugMode) 132 | { 133 | Log.e(LOG_TAG, ex.toString()); 134 | } 135 | return false; 136 | } 137 | 138 | // Only now can we start creating our actual file 139 | InputStream iss = context.getResources().openRawResource(sourceId); 140 | ReadableByteChannel rfc = Channels.newChannel(iss); 141 | FileOutputStream oss = null; 142 | try 143 | { 144 | oss = new FileOutputStream(mf); 145 | FileChannel ofc = oss.getChannel(); 146 | long pos = 0; 147 | try 148 | { 149 | long size = iss.available(); 150 | while ((pos += ofc.transferFrom(rfc, pos, size - pos)) < size) 151 | { 152 | ; 153 | } 154 | } 155 | catch (IOException ex) 156 | { 157 | if (RootTools.debugMode) 158 | { 159 | Log.e(LOG_TAG, ex.toString()); 160 | } 161 | return false; 162 | } 163 | } 164 | catch (FileNotFoundException ex) 165 | { 166 | if (RootTools.debugMode) 167 | { 168 | Log.e(LOG_TAG, ex.toString()); 169 | } 170 | return false; 171 | } 172 | finally 173 | { 174 | if (oss != null) 175 | { 176 | try 177 | { 178 | oss.flush(); 179 | oss.getFD().sync(); 180 | oss.close(); 181 | } 182 | catch (Exception e) 183 | { 184 | } 185 | } 186 | } 187 | try 188 | { 189 | iss.close(); 190 | } 191 | catch (IOException ex) 192 | { 193 | if (RootTools.debugMode) 194 | { 195 | Log.e(LOG_TAG, ex.toString()); 196 | } 197 | return false; 198 | } 199 | 200 | try 201 | { 202 | Command command = new Command(0, false, "chmod " + mode + " " + filesPath + File.separator + destName); 203 | Shell.startRootShell().add(command); 204 | commandWait(command); 205 | 206 | } 207 | catch (Exception e) 208 | { 209 | } 210 | } 211 | return true; 212 | } 213 | 214 | protected boolean isBinaryInstalled(String destName) 215 | { 216 | boolean installed = false; 217 | File mf = new File(filesPath + File.separator + destName); 218 | if (mf.exists()) 219 | { 220 | installed = true; 221 | // TODO: pass mode as argument and check it matches 222 | } 223 | return installed; 224 | } 225 | 226 | protected String getFileSignature(File f) 227 | { 228 | String signature = ""; 229 | try 230 | { 231 | signature = getStreamSignature(new FileInputStream(f)); 232 | } 233 | catch (FileNotFoundException ex) 234 | { 235 | Log.e(LOG_TAG, ex.toString()); 236 | } 237 | return signature; 238 | } 239 | 240 | /* 241 | * Note: this method will close any string passed to it 242 | */ 243 | protected String getStreamSignature(InputStream is) 244 | { 245 | String signature = ""; 246 | try 247 | { 248 | MessageDigest md = MessageDigest.getInstance("MD5"); 249 | DigestInputStream dis = new DigestInputStream(is, md); 250 | byte[] buffer = new byte[4096]; 251 | while (-1 != dis.read(buffer)) 252 | { 253 | ; 254 | } 255 | byte[] digest = md.digest(); 256 | StringBuffer sb = new StringBuffer(); 257 | 258 | for (int i = 0; i < digest.length; i++) 259 | { 260 | sb.append(Integer.toHexString(digest[i] & 0xFF)); 261 | } 262 | 263 | signature = sb.toString(); 264 | } 265 | catch (IOException ex) 266 | { 267 | Log.e(LOG_TAG, ex.toString()); 268 | } 269 | catch (NoSuchAlgorithmException ex) 270 | { 271 | Log.e(LOG_TAG, ex.toString()); 272 | } 273 | finally 274 | { 275 | try 276 | { 277 | is.close(); 278 | } 279 | catch (IOException e) 280 | { 281 | } 282 | } 283 | return signature; 284 | } 285 | 286 | private void commandWait(Command cmd) 287 | { 288 | synchronized (cmd) 289 | { 290 | try 291 | { 292 | if (!cmd.isFinished()) 293 | { 294 | cmd.wait(2000); 295 | } 296 | } 297 | catch (InterruptedException ex) 298 | { 299 | Log.e(LOG_TAG, ex.toString()); 300 | } 301 | } 302 | } 303 | } 304 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/internal/InternalVariables.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the RootTools Project: http://code.google.com/p/RootTools/ 3 | * 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks 5 | * 6 | * This code is dual-licensed under the terms of the Apache License Version 2.0 and 7 | * the terms of the General Public License (GPL) Version 2. 8 | * You may use this code according to either of these licenses as is most appropriate 9 | * for your project on a case-by-case basis. 10 | * 11 | * The terms of each license can be found in the root directory of this project's repository as well as at: 12 | * 13 | * * http://www.apache.org/licenses/LICENSE-2.0 14 | * * http://www.gnu.org/licenses/gpl-2.0.txt 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under these Licenses is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See each License for the specific language governing permissions and 20 | * limitations under that License. 21 | */ 22 | 23 | package com.stericson.RootTools.internal; 24 | 25 | import java.util.ArrayList; 26 | import java.util.regex.Pattern; 27 | 28 | import com.stericson.RootTools.containers.Mount; 29 | import com.stericson.RootTools.containers.Permissions; 30 | import com.stericson.RootTools.containers.Symlink; 31 | 32 | public class InternalVariables 33 | { 34 | 35 | // ---------------------- 36 | // # Internal Variables # 37 | // ---------------------- 38 | 39 | 40 | protected static boolean nativeToolsReady = false; 41 | protected static boolean found = false; 42 | protected static boolean processRunning = false; 43 | 44 | protected static String[] space; 45 | protected static String getSpaceFor; 46 | protected static String busyboxVersion; 47 | protected static String pid_list = ""; 48 | protected static ArrayList mounts; 49 | protected static ArrayList symlinks; 50 | protected static String inode = ""; 51 | protected static Permissions permissions; 52 | 53 | // regex to get pid out of ps line, example: 54 | // root 2611 0.0 0.0 19408 2104 pts/2 S 13:41 0:00 bash 55 | protected static final String PS_REGEX = "^\\S+\\s+([0-9]+).*$"; 56 | protected static Pattern psPattern; 57 | 58 | static 59 | { 60 | psPattern = Pattern.compile(PS_REGEX); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/internal/Remounter.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the RootTools Project: http://code.google.com/p/RootTools/ 3 | * 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks 5 | * 6 | * This code is dual-licensed under the terms of the Apache License Version 2.0 and 7 | * the terms of the General Public License (GPL) Version 2. 8 | * You may use this code according to either of these licenses as is most appropriate 9 | * for your project on a case-by-case basis. 10 | * 11 | * The terms of each license can be found in the root directory of this project's repository as well as at: 12 | * 13 | * * http://www.apache.org/licenses/LICENSE-2.0 14 | * * http://www.gnu.org/licenses/gpl-2.0.txt 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under these Licenses is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See each License for the specific language governing permissions and 20 | * limitations under that License. 21 | */ 22 | 23 | package com.stericson.RootTools.internal; 24 | 25 | import java.io.File; 26 | import java.io.IOException; 27 | import java.util.ArrayList; 28 | 29 | import com.stericson.RootShell.execution.Command; 30 | import com.stericson.RootShell.execution.Shell; 31 | import com.stericson.RootTools.Constants; 32 | import com.stericson.RootTools.RootTools; 33 | import com.stericson.RootTools.containers.Mount; 34 | 35 | public class Remounter 36 | { 37 | 38 | //------------- 39 | //# Remounter # 40 | //------------- 41 | 42 | /** 43 | * This will take a path, which can contain the file name as well, 44 | * and attempt to remount the underlying partition. 45 | *

46 | * For example, passing in the following string: 47 | * "/system/bin/some/directory/that/really/would/never/exist" 48 | * will result in /system ultimately being remounted. 49 | * However, keep in mind that the longer the path you supply, the more work this has to do, 50 | * and the slower it will run. 51 | * 52 | * @param file file path 53 | * @param mountType mount type: pass in RO (Read only) or RW (Read Write) 54 | * @return a boolean which indicates whether or not the partition 55 | * has been remounted as specified. 56 | */ 57 | 58 | public boolean remount(String file, String mountType) 59 | { 60 | 61 | //if the path has a trailing slash get rid of it. 62 | if (file.endsWith("/") && !file.equals("/")) 63 | { 64 | file = file.substring(0, file.lastIndexOf("/")); 65 | } 66 | //Make sure that what we are trying to remount is in the mount list. 67 | boolean foundMount = false; 68 | 69 | while (!foundMount) 70 | { 71 | try 72 | { 73 | for (Mount mount : RootTools.getMounts()) 74 | { 75 | RootTools.log(mount.getMountPoint().toString()); 76 | 77 | if (file.equals(mount.getMountPoint().toString())) 78 | { 79 | foundMount = true; 80 | break; 81 | } 82 | } 83 | } 84 | catch (Exception e) 85 | { 86 | if (RootTools.debugMode) 87 | { 88 | e.printStackTrace(); 89 | } 90 | return false; 91 | } 92 | if (!foundMount) 93 | { 94 | try 95 | { 96 | file = (new File(file).getParent()); 97 | } 98 | catch (Exception e) 99 | { 100 | e.printStackTrace(); 101 | return false; 102 | } 103 | } 104 | } 105 | 106 | Mount mountPoint = findMountPointRecursive(file); 107 | 108 | if (mountPoint != null) 109 | { 110 | 111 | RootTools.log(Constants.TAG, "Remounting " + mountPoint.getMountPoint().getAbsolutePath() + " as " + mountType.toLowerCase()); 112 | final boolean isMountMode = mountPoint.getFlags().contains(mountType.toLowerCase()); 113 | 114 | if (!isMountMode) 115 | { 116 | //grab an instance of the internal class 117 | try 118 | { 119 | Command command = new Command(0, 120 | true, 121 | "busybox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(), 122 | "busybox mount -o remount," + mountType.toLowerCase() + " " + file, 123 | "busybox mount -o " + mountType.toLowerCase() + ",remount " + mountPoint.getDevice().getAbsolutePath(), 124 | "busybox mount -o " + mountType.toLowerCase() + ",remount " + file, 125 | "toolbox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(), 126 | "toolbox mount -o remount," + mountType.toLowerCase() + " " + file, "toybox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(), 127 | "toolbox mount -o " + mountType.toLowerCase() + ",remount " + mountPoint.getDevice().getAbsolutePath(), 128 | "toolbox mount -o " + mountType.toLowerCase() + ",remount " + file, 129 | "mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(), 130 | "mount -o remount," + mountType.toLowerCase() + " " + file, 131 | "mount -o " + mountType.toLowerCase() + ",remount " + mountPoint.getDevice().getAbsolutePath(), 132 | "mount -o " + mountType.toLowerCase() + ",remount " + file, 133 | "toybox mount -o remount," + mountType.toLowerCase() + " " + mountPoint.getDevice().getAbsolutePath() + " " + mountPoint.getMountPoint().getAbsolutePath(), 134 | "toybox mount -o remount," + mountType.toLowerCase() + " " + file, 135 | "toybox mount -o " + mountType.toLowerCase() + ",remount " + mountPoint.getDevice().getAbsolutePath(), 136 | "toybox mount -o " + mountType.toLowerCase() + ",remount " + file); 137 | Shell.startRootShell().add(command); 138 | commandWait(command); 139 | 140 | } 141 | catch (Exception e) 142 | { 143 | } 144 | 145 | mountPoint = findMountPointRecursive(file); 146 | } 147 | 148 | if (mountPoint != null) 149 | { 150 | RootTools.log(Constants.TAG, mountPoint.getFlags() + " AND " + mountType.toLowerCase()); 151 | if (mountPoint.getFlags().contains(mountType.toLowerCase())) 152 | { 153 | RootTools.log(mountPoint.getFlags().toString()); 154 | return true; 155 | } 156 | else 157 | { 158 | RootTools.log(mountPoint.getFlags().toString()); 159 | return false; 160 | } 161 | } 162 | else 163 | { 164 | RootTools.log("mount is null, file was: " + file + " mountType was: " + mountType); 165 | } 166 | } 167 | else 168 | { 169 | RootTools.log("mount is null, file was: " + file + " mountType was: " + mountType); 170 | } 171 | 172 | return false; 173 | } 174 | 175 | private Mount findMountPointRecursive(String file) 176 | { 177 | try 178 | { 179 | ArrayList mounts = RootTools.getMounts(); 180 | 181 | for (File path = new File(file); path != null; ) 182 | { 183 | for (Mount mount : mounts) 184 | { 185 | if (mount.getMountPoint().equals(path)) 186 | { 187 | return mount; 188 | } 189 | } 190 | } 191 | 192 | return null; 193 | 194 | } 195 | catch (IOException e) 196 | { 197 | if (RootTools.debugMode) 198 | { 199 | e.printStackTrace(); 200 | } 201 | } 202 | catch (Exception e) 203 | { 204 | if (RootTools.debugMode) 205 | { 206 | e.printStackTrace(); 207 | } 208 | } 209 | 210 | return null; 211 | } 212 | 213 | private void commandWait(Command cmd) 214 | { 215 | synchronized (cmd) 216 | { 217 | try 218 | { 219 | if (!cmd.isFinished()) 220 | { 221 | cmd.wait(2000); 222 | } 223 | } 224 | catch (InterruptedException e) 225 | { 226 | e.printStackTrace(); 227 | } 228 | } 229 | } 230 | } 231 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/internal/RootToolsInternalMethods.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the RootTools Project: http://code.google.com/p/RootTools/ 3 | * 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks 5 | * 6 | * This code is dual-licensed under the terms of the Apache License Version 2.0 and 7 | * the terms of the General Public License (GPL) Version 2. 8 | * You may use this code according to either of these licenses as is most appropriate 9 | * for your project on a case-by-case basis. 10 | * 11 | * The terms of each license can be found in the root directory of this project's repository as well as at: 12 | * 13 | * * http://www.apache.org/licenses/LICENSE-2.0 14 | * * http://www.gnu.org/licenses/gpl-2.0.txt 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under these Licenses is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See each License for the specific language governing permissions and 20 | * limitations under that License. 21 | */ 22 | 23 | package com.stericson.RootTools.internal; 24 | 25 | import android.app.Activity; 26 | import android.content.Context; 27 | import android.content.Intent; 28 | import android.net.Uri; 29 | import android.os.Build; 30 | import android.os.Environment; 31 | import android.os.StatFs; 32 | import android.util.Log; 33 | 34 | import com.stericson.RootTools.Constants; 35 | import com.stericson.RootShell.RootShell; 36 | import com.stericson.RootShell.execution.Command; 37 | import com.stericson.RootShell.execution.Shell; 38 | import com.stericson.RootTools.RootTools; 39 | import com.stericson.RootTools.containers.Mount; 40 | import com.stericson.RootTools.containers.Permissions; 41 | import com.stericson.RootTools.containers.Symlink; 42 | 43 | import java.io.File; 44 | import java.io.FileReader; 45 | import java.io.IOException; 46 | import java.io.LineNumberReader; 47 | import java.util.ArrayList; 48 | import java.util.List; 49 | import java.util.Locale; 50 | import java.util.concurrent.TimeoutException; 51 | import java.util.regex.Matcher; 52 | 53 | public final class RootToolsInternalMethods { 54 | 55 | // -------------------- 56 | // # Internal methods # 57 | // -------------------- 58 | 59 | protected RootToolsInternalMethods() { 60 | } 61 | 62 | public static void getInstance() { 63 | //this will allow RootTools to be the only one to get an instance of this class. 64 | RootTools.setRim(new RootToolsInternalMethods()); 65 | } 66 | 67 | public Permissions getPermissions(String line) { 68 | 69 | String[] lineArray = line.split(" "); 70 | String rawPermissions = lineArray[0]; 71 | 72 | if (rawPermissions.length() == 10 73 | && (rawPermissions.charAt(0) == '-' 74 | || rawPermissions.charAt(0) == 'd' || rawPermissions 75 | .charAt(0) == 'l') 76 | && (rawPermissions.charAt(1) == '-' || rawPermissions.charAt(1) == 'r') 77 | && (rawPermissions.charAt(2) == '-' || rawPermissions.charAt(2) == 'w')) { 78 | RootTools.log(rawPermissions); 79 | 80 | Permissions permissions = new Permissions(); 81 | 82 | permissions.setType(rawPermissions.substring(0, 1)); 83 | 84 | RootTools.log(permissions.getType()); 85 | 86 | permissions.setUserPermissions(rawPermissions.substring(1, 4)); 87 | 88 | RootTools.log(permissions.getUserPermissions()); 89 | 90 | permissions.setGroupPermissions(rawPermissions.substring(4, 7)); 91 | 92 | RootTools.log(permissions.getGroupPermissions()); 93 | 94 | permissions.setOtherPermissions(rawPermissions.substring(7, 10)); 95 | 96 | RootTools.log(permissions.getOtherPermissions()); 97 | 98 | StringBuilder finalPermissions = new StringBuilder(); 99 | finalPermissions.append(parseSpecialPermissions(rawPermissions)); 100 | finalPermissions.append(parsePermissions(permissions.getUserPermissions())); 101 | finalPermissions.append(parsePermissions(permissions.getGroupPermissions())); 102 | finalPermissions.append(parsePermissions(permissions.getOtherPermissions())); 103 | 104 | permissions.setPermissions(Integer.parseInt(finalPermissions.toString())); 105 | 106 | return permissions; 107 | } 108 | 109 | return null; 110 | } 111 | 112 | public int parsePermissions(String permission) { 113 | permission = permission.toLowerCase(Locale.US); 114 | int tmp; 115 | if (permission.charAt(0) == 'r') { 116 | tmp = 4; 117 | } else { 118 | tmp = 0; 119 | } 120 | 121 | RootTools.log("permission " + tmp); 122 | RootTools.log("character " + permission.charAt(0)); 123 | 124 | if (permission.charAt(1) == 'w') { 125 | tmp += 2; 126 | } else { 127 | tmp += 0; 128 | } 129 | 130 | RootTools.log("permission " + tmp); 131 | RootTools.log("character " + permission.charAt(1)); 132 | 133 | if (permission.charAt(2) == 'x' || permission.charAt(2) == 's' 134 | || permission.charAt(2) == 't') { 135 | tmp += 1; 136 | } else { 137 | tmp += 0; 138 | } 139 | 140 | RootTools.log("permission " + tmp); 141 | RootTools.log("character " + permission.charAt(2)); 142 | 143 | return tmp; 144 | } 145 | 146 | public int parseSpecialPermissions(String permission) { 147 | int tmp = 0; 148 | if (permission.charAt(2) == 's') { 149 | tmp += 4; 150 | } 151 | 152 | if (permission.charAt(5) == 's') { 153 | tmp += 2; 154 | } 155 | 156 | if (permission.charAt(8) == 't') { 157 | tmp += 1; 158 | } 159 | 160 | RootTools.log("special permissions " + tmp); 161 | 162 | return tmp; 163 | } 164 | 165 | /** 166 | * Copys a file to a destination. Because cp is not available on all android devices, we have a 167 | * fallback on the cat command 168 | * 169 | * @param source example: /data/data/org.adaway/files/hosts 170 | * @param destination example: /system/etc/hosts 171 | * @param remountAsRw remounts the destination as read/write before writing to it 172 | * @param preserveFileAttributes tries to copy file attributes from source to destination, if only cat is available 173 | * only permissions are preserved 174 | * @return true if it was successfully copied 175 | */ 176 | public boolean copyFile(String source, String destination, boolean remountAsRw, 177 | boolean preserveFileAttributes) { 178 | 179 | Command command = null; 180 | boolean result = true; 181 | 182 | try { 183 | // mount destination as rw before writing to it 184 | if (remountAsRw) { 185 | RootTools.remount(destination, "RW"); 186 | } 187 | 188 | // if cp is available and has appropriate permissions 189 | if (checkUtil("cp")) { 190 | RootTools.log("cp command is available!"); 191 | 192 | if (preserveFileAttributes) { 193 | command = new Command(0, false, "cp -fp " + source + " " + destination); 194 | Shell.startRootShell().add(command); 195 | commandWait(Shell.startRootShell(), command); 196 | 197 | //ensure that the file was copied, an exitcode of zero means success 198 | result = command.getExitCode() == 0; 199 | 200 | } else { 201 | command = new Command(0, false, "cp -f " + source + " " + destination); 202 | Shell.startRootShell().add(command); 203 | commandWait(Shell.startRootShell(), command); 204 | 205 | //ensure that the file was copied, an exitcode of zero means success 206 | result = command.getExitCode() == 0; 207 | 208 | } 209 | } else { 210 | if (checkUtil("busybox") && hasUtil("cp", "busybox")) { 211 | RootTools.log("busybox cp command is available!"); 212 | 213 | if (preserveFileAttributes) { 214 | command = new Command(0, false, "busybox cp -fp " + source + " " + destination); 215 | Shell.startRootShell().add(command); 216 | commandWait(Shell.startRootShell(), command); 217 | 218 | } else { 219 | command = new Command(0, false, "busybox cp -f " + source + " " + destination); 220 | Shell.startRootShell().add(command); 221 | commandWait(Shell.startRootShell(), command); 222 | 223 | } 224 | } else { // if cp is not available use cat 225 | // if cat is available and has appropriate permissions 226 | if (checkUtil("cat")) { 227 | RootTools.log("cp is not available, use cat!"); 228 | 229 | int filePermission = -1; 230 | if (preserveFileAttributes) { 231 | // get permissions of source before overwriting 232 | Permissions permissions = getFilePermissionsSymlinks(source); 233 | filePermission = permissions.getPermissions(); 234 | } 235 | 236 | // copy with cat 237 | command = new Command(0, false, "cat " + source + " > " + destination); 238 | Shell.startRootShell().add(command); 239 | commandWait(Shell.startRootShell(), command); 240 | 241 | if (preserveFileAttributes) { 242 | // set premissions of source to destination 243 | command = new Command(0, false, "chmod " + filePermission + " " + destination); 244 | Shell.startRootShell().add(command); 245 | commandWait(Shell.startRootShell(), command); 246 | } 247 | } else { 248 | result = false; 249 | } 250 | } 251 | } 252 | 253 | // mount destination back to ro 254 | if (remountAsRw) { 255 | RootTools.remount(destination, "RO"); 256 | } 257 | } catch (Exception e) { 258 | e.printStackTrace(); 259 | result = false; 260 | } 261 | 262 | if (command != null) { 263 | //ensure that the file was copied, an exitcode of zero means success 264 | result = command.getExitCode() == 0; 265 | } 266 | 267 | return result; 268 | } 269 | 270 | /** 271 | * This will check a given binary, determine if it exists and determine that 272 | * it has either the permissions 755, 775, or 777. 273 | * 274 | * @param util Name of the utility to check. 275 | * @return boolean to indicate whether the binary is installed and has 276 | * appropriate permissions. 277 | */ 278 | public boolean checkUtil(String util) { 279 | List foundPaths = RootShell.findBinary(util, true); 280 | if (foundPaths.size() > 0) { 281 | 282 | for (String path : foundPaths) { 283 | Permissions permissions = RootTools 284 | .getFilePermissionsSymlinks(path + "/" + util); 285 | 286 | if (permissions != null) { 287 | String permission; 288 | 289 | if (Integer.toString(permissions.getPermissions()).length() > 3) { 290 | permission = Integer.toString(permissions.getPermissions()).substring(1); 291 | } else { 292 | permission = Integer.toString(permissions.getPermissions()); 293 | } 294 | 295 | if (permission.equals("755") || permission.equals("777") 296 | || permission.equals("775")) { 297 | RootTools.utilPath = path + "/" + util; 298 | return true; 299 | } 300 | } 301 | } 302 | } 303 | 304 | return false; 305 | 306 | } 307 | 308 | /** 309 | * Deletes a file or directory 310 | * 311 | * @param target example: /data/data/org.adaway/files/hosts 312 | * @param remountAsRw remounts the destination as read/write before writing to it 313 | * @return true if it was successfully deleted 314 | */ 315 | public boolean deleteFileOrDirectory(String target, boolean remountAsRw) { 316 | boolean result = true; 317 | 318 | try { 319 | // mount destination as rw before writing to it 320 | if (remountAsRw) { 321 | RootTools.remount(target, "RW"); 322 | } 323 | 324 | if (hasUtil("rm", "toolbox")) { 325 | RootTools.log("rm command is available!"); 326 | 327 | Command command = new Command(0, false, "rm -r " + target); 328 | Shell.startRootShell().add(command); 329 | commandWait(Shell.startRootShell(), command); 330 | 331 | if (command.getExitCode() != 0) { 332 | RootTools.log("target not exist or unable to delete file"); 333 | result = false; 334 | } 335 | } else { 336 | if (checkUtil("busybox") && hasUtil("rm", "busybox")) { 337 | RootTools.log("busybox rm command is available!"); 338 | 339 | Command command = new Command(0, false, "busybox rm -rf " + target); 340 | Shell.startRootShell().add(command); 341 | commandWait(Shell.startRootShell(), command); 342 | 343 | if (command.getExitCode() != 0) { 344 | RootTools.log("target not exist or unable to delete file"); 345 | result = false; 346 | } 347 | } 348 | } 349 | 350 | // mount destination back to ro 351 | if (remountAsRw) { 352 | RootTools.remount(target, "RO"); 353 | } 354 | } catch (Exception e) { 355 | e.printStackTrace(); 356 | result = false; 357 | } 358 | 359 | return result; 360 | } 361 | 362 | /** 363 | * This will try and fix a given binary. (This is for Busybox applets or Toolbox applets) By 364 | * "fix", I mean it will try and symlink the binary from either toolbox or Busybox and fix the 365 | * permissions if the permissions are not correct. 366 | * 367 | * @param util Name of the utility to fix. 368 | * @param utilPath path to the toolbox that provides ln, rm, and chmod. This can be a blank string, a 369 | * path to a binary that will provide these, or you can use 370 | * RootTools.getWorkingToolbox() 371 | */ 372 | public void fixUtil(String util, String utilPath) { 373 | try { 374 | RootTools.remount("/system", "rw"); 375 | 376 | List foundPaths = RootShell.findBinary(util, true); 377 | 378 | if (foundPaths.size() > 0) { 379 | for (String path : foundPaths) { 380 | Command command = new Command(0, false, utilPath + " rm " + path + "/" + util); 381 | RootShell.getShell(true).add(command); 382 | commandWait(RootShell.getShell(true), command); 383 | 384 | } 385 | 386 | Command command = new Command(0, false, utilPath + " ln -s " + utilPath + " /system/bin/" + util, utilPath + " chmod 0755 /system/bin/" + util); 387 | RootShell.getShell(true).add(command); 388 | commandWait(RootShell.getShell(true), command); 389 | 390 | } 391 | 392 | RootTools.remount("/system", "ro"); 393 | } catch (Exception e) { 394 | } 395 | } 396 | 397 | /** 398 | * This will check an array of binaries, determine if they exist and determine that it has 399 | * either the permissions 755, 775, or 777. If an applet is not setup correctly it will try and 400 | * fix it. (This is for Busybox applets or Toolbox applets) 401 | * 402 | * @param utils Name of the utility to check. 403 | * @return boolean to indicate whether the operation completed. Note that this is not indicative 404 | * of whether the problem was fixed, just that the method did not encounter any 405 | * exceptions. 406 | * @throws Exception if the operation cannot be completed. 407 | */ 408 | public boolean fixUtils(String[] utils) throws Exception { 409 | 410 | for (String util : utils) { 411 | if (!checkUtil(util)) { 412 | if (checkUtil("busybox")) { 413 | if (hasUtil(util, "busybox")) { 414 | fixUtil(util, RootTools.utilPath); 415 | } 416 | } else { 417 | if (checkUtil("toolbox")) { 418 | if (hasUtil(util, "toolbox")) { 419 | fixUtil(util, RootTools.utilPath); 420 | } 421 | } else { 422 | return false; 423 | } 424 | } 425 | } 426 | } 427 | 428 | return true; 429 | } 430 | 431 | /** 432 | * This will return an List of Strings. Each string represents an applet available from BusyBox. 433 | *

434 | * 435 | * @param path Path to the busybox binary that you want the list of applets from. 436 | * @return null If we cannot return the list of applets. 437 | */ 438 | public List getBusyBoxApplets(String path) throws Exception { 439 | 440 | if (path != null && !path.endsWith("/") && !path.equals("")) { 441 | path += "/"; 442 | } else if (path == null) { 443 | //Don't know what the user wants to do...what am I pshycic? 444 | throw new Exception("Path is null, please specifiy a path"); 445 | } 446 | 447 | final List results = new ArrayList(); 448 | 449 | Command command = new Command(Constants.BBA, false, path + "busybox --list") { 450 | @Override 451 | public void commandOutput(int id, String line) { 452 | if (id == Constants.BBA) { 453 | if (!line.trim().equals("") && !line.trim().contains("not found") && !line.trim().contains("file busy")) { 454 | results.add(line); 455 | } 456 | } 457 | 458 | super.commandOutput(id, line); 459 | } 460 | }; 461 | 462 | //try without root first... 463 | RootShell.getShell(false).add(command); 464 | commandWait(RootShell.getShell(false), command); 465 | 466 | if (results.size() <= 0) { 467 | //try with root... 468 | 469 | command = new Command(Constants.BBA, false, path + "busybox --list") { 470 | @Override 471 | public void commandOutput(int id, String line) { 472 | if (id == Constants.BBA) { 473 | if (!line.trim().equals("") && !line.trim().contains("not found") && !line.trim().contains("file busy")) { 474 | results.add(line); 475 | } 476 | } 477 | 478 | super.commandOutput(id, line); 479 | } 480 | }; 481 | 482 | RootShell.getShell(true).add(command); 483 | commandWait(RootShell.getShell(true), command); 484 | } 485 | 486 | return results; 487 | } 488 | 489 | /** 490 | * @return BusyBox version if found, "" if not found. 491 | */ 492 | public String getBusyBoxVersion(String path) { 493 | 494 | final StringBuilder version = new StringBuilder(); 495 | 496 | if (!path.equals("") && !path.endsWith("/")) { 497 | path += "/"; 498 | } 499 | 500 | try { 501 | Command command = new Command(Constants.BBV, false, path + "busybox") { 502 | @Override 503 | public void commandOutput(int id, String line) { 504 | line = line.trim(); 505 | 506 | boolean foundVersion = false; 507 | 508 | if (id == Constants.BBV) { 509 | RootTools.log("Version Output: " + line); 510 | 511 | String[] temp = line.split(" "); 512 | 513 | if (temp.length > 1 && temp[1].contains("v1.") && !foundVersion) { 514 | foundVersion = true; 515 | version.append(temp[1]); 516 | RootTools.log("Found Version: " + version.toString()); 517 | } 518 | } 519 | 520 | super.commandOutput(id, line); 521 | } 522 | }; 523 | 524 | //try without root first 525 | RootTools.log("Getting BusyBox Version without root"); 526 | Shell shell = RootTools.getShell(false); 527 | shell.add(command); 528 | commandWait(shell, command); 529 | 530 | if (version.length() <= 0) { 531 | 532 | command = new Command(Constants.BBV, false, path + "busybox") { 533 | @Override 534 | public void commandOutput(int id, String line) { 535 | line = line.trim(); 536 | 537 | boolean foundVersion = false; 538 | 539 | if (id == Constants.BBV) { 540 | RootTools.log("Version Output: " + line); 541 | 542 | String[] temp = line.split(" "); 543 | 544 | if (temp.length > 1 && temp[1].contains("v1.") && !foundVersion) { 545 | foundVersion = true; 546 | version.append(temp[1]); 547 | RootTools.log("Found Version: " + version.toString()); 548 | } 549 | } 550 | 551 | super.commandOutput(id, line); 552 | } 553 | }; 554 | 555 | RootTools.log("Getting BusyBox Version with root"); 556 | Shell rootShell = RootTools.getShell(true); 557 | //Now look for it... 558 | rootShell.add(command); 559 | commandWait(rootShell, command); 560 | } 561 | 562 | } catch (Exception e) { 563 | RootTools.log("BusyBox was not found, more information MAY be available with Debugging on."); 564 | return ""; 565 | } 566 | 567 | RootTools.log("Returning found version: " + version.toString()); 568 | return version.toString(); 569 | } 570 | 571 | /** 572 | * @return long Size, converted to kilobytes (from xxx or xxxm or xxxk etc.) 573 | */ 574 | public long getConvertedSpace(String spaceStr) { 575 | try { 576 | double multiplier = 1.0; 577 | char c; 578 | StringBuffer sb = new StringBuffer(); 579 | for (int i = 0; i < spaceStr.length(); i++) { 580 | c = spaceStr.charAt(i); 581 | if (!Character.isDigit(c) && c != '.') { 582 | if (c == 'm' || c == 'M') { 583 | multiplier = 1024.0; 584 | } else if (c == 'g' || c == 'G') { 585 | multiplier = 1024.0 * 1024.0; 586 | } 587 | break; 588 | } 589 | sb.append(spaceStr.charAt(i)); 590 | } 591 | return (long) Math.ceil(Double.valueOf(sb.toString()) * multiplier); 592 | } catch (Exception e) { 593 | return -1; 594 | } 595 | } 596 | 597 | /** 598 | * This method will return the inode number of a file. This method is dependent on having a version of 599 | * ls that supports the -i parameter. 600 | * 601 | * @param file path to the file that you wish to return the inode number 602 | * @return String The inode number for this file or "" if the inode number could not be found. 603 | */ 604 | public String getInode(String file) { 605 | try { 606 | Command command = new Command(Constants.GI, false, "/data/local/ls -i " + file) { 607 | 608 | @Override 609 | public void commandOutput(int id, String line) { 610 | if (id == Constants.GI) { 611 | if (!line.trim().equals("") && Character.isDigit(line.trim().substring(0, 1).toCharArray()[0])) { 612 | InternalVariables.inode = line.trim().split(" ")[0]; 613 | } 614 | } 615 | 616 | super.commandOutput(id, line); 617 | } 618 | }; 619 | Shell.startRootShell().add(command); 620 | commandWait(Shell.startRootShell(), command); 621 | 622 | return InternalVariables.inode; 623 | } catch (Exception ignore) { 624 | return ""; 625 | } 626 | } 627 | 628 | public boolean isNativeToolsReady(int nativeToolsId, Context context) { 629 | RootTools.log("Preparing Native Tools"); 630 | InternalVariables.nativeToolsReady = false; 631 | 632 | Installer installer; 633 | try { 634 | installer = new Installer(context); 635 | } catch (IOException ex) { 636 | if (RootTools.debugMode) { 637 | ex.printStackTrace(); 638 | } 639 | return false; 640 | } 641 | 642 | if (installer.isBinaryInstalled("nativetools")) { 643 | InternalVariables.nativeToolsReady = true; 644 | } else { 645 | InternalVariables.nativeToolsReady = installer.installBinary(nativeToolsId, 646 | "nativetools", "700"); 647 | } 648 | return InternalVariables.nativeToolsReady; 649 | } 650 | 651 | /** 652 | * @param file String that represent the file, including the full path to the 653 | * file and its name. 654 | * @return An instance of the class permissions from which you can get the 655 | * permissions of the file or if the file could not be found or 656 | * permissions couldn't be determined then permissions will be null. 657 | */ 658 | public Permissions getFilePermissionsSymlinks(String file) { 659 | RootTools.log("Checking permissions for " + file); 660 | if (RootTools.exists(file)) { 661 | RootTools.log(file + " was found."); 662 | try { 663 | 664 | Command command = new Command( 665 | Constants.FPS, false, "ls -l " + file, 666 | "busybox ls -l " + file, 667 | "/system/bin/failsafe/toolbox ls -l " + file, 668 | "toolbox ls -l " + file) { 669 | @Override 670 | public void commandOutput(int id, String line) { 671 | if (id == Constants.FPS) { 672 | String symlink_final = ""; 673 | 674 | String[] lineArray = line.split(" "); 675 | if (lineArray[0].length() != 10) { 676 | super.commandOutput(id, line); 677 | return; 678 | } 679 | 680 | RootTools.log("Line " + line); 681 | 682 | try { 683 | String[] symlink = line.split(" "); 684 | if (symlink[symlink.length - 2].equals("->")) { 685 | RootTools.log("Symlink found."); 686 | symlink_final = symlink[symlink.length - 1]; 687 | } 688 | } catch (Exception e) { 689 | } 690 | 691 | try { 692 | InternalVariables.permissions = getPermissions(line); 693 | if (InternalVariables.permissions != null) { 694 | InternalVariables.permissions.setSymlink(symlink_final); 695 | } 696 | } catch (Exception e) { 697 | RootTools.log(e.getMessage()); 698 | } 699 | } 700 | 701 | super.commandOutput(id, line); 702 | } 703 | }; 704 | RootShell.getShell(true).add(command); 705 | commandWait(RootShell.getShell(true), command); 706 | 707 | return InternalVariables.permissions; 708 | 709 | } catch (Exception e) { 710 | RootTools.log(e.getMessage()); 711 | return null; 712 | } 713 | } 714 | 715 | return null; 716 | } 717 | 718 | /** 719 | * This will return an ArrayList of the class Mount. The class mount contains the following 720 | * property's: device mountPoint type flags 721 | *

722 | * These will provide you with any information you need to work with the mount points. 723 | * 724 | * @return ArrayList an ArrayList of the class Mount. 725 | * @throws Exception if we cannot return the mount points. 726 | */ 727 | public ArrayList getMounts() throws Exception { 728 | 729 | InternalVariables.mounts = new ArrayList<>(); 730 | 731 | if(null == InternalVariables.mounts || InternalVariables.mounts.isEmpty()) { 732 | Shell shell = RootTools.getShell(true); 733 | 734 | Command cmd = new Command(Constants.GET_MOUNTS, 735 | false, 736 | "cat /proc/mounts") { 737 | 738 | @Override 739 | public void commandOutput(int id, String line) { 740 | if (id == Constants.GET_MOUNTS) { 741 | RootTools.log(line); 742 | 743 | String[] fields = line.split(" "); 744 | 745 | if(fields.length > 3) { 746 | InternalVariables.mounts.add(new Mount(new File(fields[0]), // device 747 | new File(fields[1]), // mountPoint 748 | fields[2], // fstype 749 | fields[3] // flags 750 | )); 751 | } 752 | } 753 | 754 | super.commandOutput(id, line); 755 | } 756 | }; 757 | shell.add(cmd); 758 | this.commandWait(shell, cmd); 759 | } 760 | 761 | return InternalVariables.mounts; 762 | } 763 | 764 | /** 765 | * This will tell you how the specified mount is mounted. rw, ro, etc... 766 | *

767 | * 768 | * @param path mount you want to check 769 | * @return String What the mount is mounted as. 770 | * @throws Exception if we cannot determine how the mount is mounted. 771 | */ 772 | public String getMountedAs(String path) throws Exception { 773 | InternalVariables.mounts = getMounts(); 774 | String mp; 775 | if (InternalVariables.mounts != null) { 776 | for (Mount mount : InternalVariables.mounts) { 777 | 778 | mp = mount.getMountPoint().getAbsolutePath(); 779 | 780 | if (mp.equals("/")) { 781 | if (path.equals("/")) { 782 | return (String) mount.getFlags().toArray()[0]; 783 | } else { 784 | continue; 785 | } 786 | } 787 | 788 | if (path.equals(mp) || path.startsWith(mp + "/")) { 789 | RootTools.log((String) mount.getFlags().toArray()[0]); 790 | return (String) mount.getFlags().toArray()[0]; 791 | } 792 | } 793 | 794 | throw new Exception(); 795 | } else { 796 | throw new Exception(); 797 | } 798 | } 799 | 800 | /** 801 | * Get the space for a desired partition. 802 | * 803 | * @param path The partition to find the space for. 804 | * @return the amount if space found within the desired partition. If the space was not found 805 | * then the value is -1 806 | * @throws TimeoutException 807 | */ 808 | public long getSpace(String path) { 809 | InternalVariables.getSpaceFor = path; 810 | boolean found = false; 811 | RootTools.log("Looking for Space"); 812 | try { 813 | final Command command = new Command(Constants.GS, false, "df " + path) { 814 | 815 | @Override 816 | public void commandOutput(int id, String line) { 817 | if (id == Constants.GS) { 818 | if (line.contains(InternalVariables.getSpaceFor.trim())) { 819 | InternalVariables.space = line.split(" "); 820 | } 821 | } 822 | 823 | super.commandOutput(id, line); 824 | } 825 | }; 826 | Shell.startRootShell().add(command); 827 | commandWait(Shell.startRootShell(), command); 828 | 829 | } catch (Exception e) { 830 | } 831 | 832 | if (InternalVariables.space != null) { 833 | RootTools.log("First Method"); 834 | 835 | for (String spaceSearch : InternalVariables.space) { 836 | 837 | RootTools.log(spaceSearch); 838 | 839 | if (found) { 840 | return getConvertedSpace(spaceSearch); 841 | } else if (spaceSearch.equals("used,")) { 842 | found = true; 843 | } 844 | } 845 | 846 | // Try this way 847 | int count = 0, targetCount = 3; 848 | 849 | RootTools.log("Second Method"); 850 | 851 | if (InternalVariables.space[0].length() <= 5) { 852 | targetCount = 2; 853 | } 854 | 855 | for (String spaceSearch : InternalVariables.space) { 856 | 857 | RootTools.log(spaceSearch); 858 | if (spaceSearch.length() > 0) { 859 | RootTools.log(spaceSearch + ("Valid")); 860 | if (count == targetCount) { 861 | return getConvertedSpace(spaceSearch); 862 | } 863 | count++; 864 | } 865 | } 866 | } 867 | RootTools.log("Returning -1, space could not be determined."); 868 | return -1; 869 | } 870 | 871 | /** 872 | * This will return a String that represent the symlink for a specified file. 873 | *

874 | * 875 | * @param file file to get the Symlink for. (must have absolute path) 876 | * @return String a String that represent the symlink for a specified file or an 877 | * empty string if no symlink exists. 878 | */ 879 | public String getSymlink(String file) { 880 | RootTools.log("Looking for Symlink for " + file); 881 | 882 | try { 883 | final List results = new ArrayList(); 884 | 885 | Command command = new Command(Constants.GSYM, false, "ls -l " + file) { 886 | 887 | @Override 888 | public void commandOutput(int id, String line) { 889 | if (id == Constants.GSYM) { 890 | if (!line.trim().equals("")) { 891 | results.add(line); 892 | } 893 | } 894 | 895 | super.commandOutput(id, line); 896 | } 897 | }; 898 | Shell.startRootShell().add(command); 899 | commandWait(Shell.startRootShell(), command); 900 | 901 | String[] symlink = results.get(0).split(" "); 902 | if (symlink.length > 2 && symlink[symlink.length - 2].equals("->")) { 903 | RootTools.log("Symlink found."); 904 | 905 | String final_symlink; 906 | 907 | if (!symlink[symlink.length - 1].equals("") && !symlink[symlink.length - 1].contains("/")) { 908 | //We assume that we need to get the path for this symlink as it is probably not absolute. 909 | List paths = RootShell.findBinary(symlink[symlink.length - 1], true); 910 | if (paths.size() > 0) { 911 | //We return the first found location. 912 | final_symlink = paths.get(0) + symlink[symlink.length - 1]; 913 | } else { 914 | //we couldnt find a path, return the symlink by itself. 915 | final_symlink = symlink[symlink.length - 1]; 916 | } 917 | } else { 918 | final_symlink = symlink[symlink.length - 1]; 919 | } 920 | 921 | return final_symlink; 922 | } 923 | } catch (Exception e) { 924 | if (RootTools.debugMode) { 925 | e.printStackTrace(); 926 | } 927 | } 928 | 929 | RootTools.log("Symlink not found"); 930 | return ""; 931 | } 932 | 933 | /** 934 | * This will return an ArrayList of the class Symlink. The class Symlink contains the following 935 | * property's: path SymplinkPath 936 | *

937 | * These will provide you with any Symlinks in the given path. 938 | * 939 | * @param path path to search for Symlinks. 940 | * @return ArrayList an ArrayList of the class Symlink. 941 | * @throws Exception if we cannot return the Symlinks. 942 | */ 943 | public ArrayList getSymlinks(String path) throws Exception { 944 | 945 | // this command needs find 946 | if (!checkUtil("find")) { 947 | throw new Exception(); 948 | } 949 | 950 | InternalVariables.symlinks = new ArrayList<>(); 951 | 952 | Command command = new Command(0, false, "find " + path + " -type l -exec ls -l {} \\;") { 953 | @Override 954 | public void commandOutput(int id, String line) { 955 | if (id == Constants.GET_SYMLINKS) { 956 | RootTools.log(line); 957 | 958 | String[] fields = line.split(" "); 959 | InternalVariables.symlinks.add(new Symlink(new File(fields[fields.length - 3]), // file 960 | new File(fields[fields.length - 1]) // SymlinkPath 961 | )); 962 | 963 | } 964 | 965 | super.commandOutput(id, line); 966 | } 967 | }; 968 | Shell.startRootShell().add(command); 969 | commandWait(Shell.startRootShell(), command); 970 | 971 | if (InternalVariables.symlinks != null) { 972 | return InternalVariables.symlinks; 973 | } else { 974 | throw new Exception(); 975 | } 976 | } 977 | 978 | /** 979 | * This will return to you a string to be used in your shell commands which will represent the 980 | * valid working toolbox with correct permissions. For instance, if Busybox is available it will 981 | * return "busybox", if busybox is not available but toolbox is then it will return "toolbox" 982 | * 983 | * @return String that indicates the available toolbox to use for accessing applets. 984 | */ 985 | public String getWorkingToolbox() { 986 | if (RootTools.checkUtil("busybox")) { 987 | return "busybox"; 988 | } else if (RootTools.checkUtil("toolbox")) { 989 | return "toolbox"; 990 | } else { 991 | return ""; 992 | } 993 | } 994 | 995 | /** 996 | * Checks if there is enough Space on SDCard 997 | * 998 | * @param updateSize size to Check (long) 999 | * @return true if the Update will fit on SDCard, false if not enough 1000 | * space on SDCard. Will also return false, if the SDCard is not mounted as 1001 | * read/write 1002 | */ 1003 | @SuppressWarnings("deprecation") 1004 | public boolean hasEnoughSpaceOnSdCard(long updateSize) { 1005 | RootTools.log("Checking SDcard size and that it is mounted as RW"); 1006 | String status = Environment.getExternalStorageState(); 1007 | if (!status.equals(Environment.MEDIA_MOUNTED)) { 1008 | return false; 1009 | } 1010 | File path = Environment.getExternalStorageDirectory(); 1011 | StatFs stat = new StatFs(path.getPath()); 1012 | long blockSize = 0; 1013 | long availableBlocks = 0; 1014 | if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR2) { 1015 | blockSize = stat.getBlockSize(); 1016 | availableBlocks = stat.getAvailableBlocks(); 1017 | } else { 1018 | blockSize = stat.getBlockSizeLong(); 1019 | availableBlocks = stat.getAvailableBlocksLong(); 1020 | } 1021 | return (updateSize < availableBlocks * blockSize); 1022 | } 1023 | 1024 | /** 1025 | * Checks whether the toolbox or busybox binary contains a specific util 1026 | * 1027 | * @param util 1028 | * @param box Should contain "toolbox" or "busybox" 1029 | * @return true if it contains this util 1030 | */ 1031 | public boolean hasUtil(final String util, final String box) { 1032 | 1033 | InternalVariables.found = false; 1034 | 1035 | // only for busybox and toolbox 1036 | if (!(box.endsWith("toolbox") || box.endsWith("busybox"))) { 1037 | return false; 1038 | } 1039 | 1040 | try { 1041 | 1042 | Command command = new Command(0, false, box.endsWith("toolbox") ? box + " " + util : box + " --list") { 1043 | 1044 | @Override 1045 | public void commandOutput(int id, String line) { 1046 | if (box.endsWith("toolbox")) { 1047 | if (!line.contains("no such tool")) { 1048 | InternalVariables.found = true; 1049 | } 1050 | } else if (box.endsWith("busybox")) { 1051 | // go through all lines of busybox --list 1052 | if (line.contains(util)) { 1053 | RootTools.log("Found util!"); 1054 | InternalVariables.found = true; 1055 | } 1056 | } 1057 | 1058 | super.commandOutput(id, line); 1059 | } 1060 | }; 1061 | RootTools.getShell(true).add(command); 1062 | commandWait(RootTools.getShell(true), command); 1063 | 1064 | if (InternalVariables.found) { 1065 | RootTools.log("Box contains " + util + " util!"); 1066 | return true; 1067 | } else { 1068 | RootTools.log("Box does not contain " + util + " util!"); 1069 | return false; 1070 | } 1071 | } catch (Exception e) { 1072 | RootTools.log(e.getMessage()); 1073 | return false; 1074 | } 1075 | } 1076 | 1077 | /** 1078 | * This method can be used to unpack a binary from the raw resources folder and store it in 1079 | * /data/data/app.package/files/ This is typically useful if you provide your own C- or 1080 | * C++-based binary. This binary can then be executed using sendShell() and its full path. 1081 | * 1082 | * @param context the current activity's Context 1083 | * @param sourceId resource id; typically R.raw.id 1084 | * @param destName destination file name; appended to /data/data/app.package/files/ 1085 | * @param mode chmod value for this file 1086 | * @return a boolean which indicates whether or not we were able to create the new 1087 | * file. 1088 | */ 1089 | public boolean installBinary(Context context, int sourceId, String destName, String mode) { 1090 | Installer installer; 1091 | 1092 | try { 1093 | installer = new Installer(context); 1094 | } catch (IOException ex) { 1095 | if (RootTools.debugMode) { 1096 | ex.printStackTrace(); 1097 | } 1098 | return false; 1099 | } 1100 | 1101 | return (installer.installBinary(sourceId, destName, mode)); 1102 | } 1103 | 1104 | /** 1105 | * This method checks whether a binary is installed. 1106 | * 1107 | * @param context the current activity's Context 1108 | * @param binaryName binary file name; appended to /data/data/app.package/files/ 1109 | * @return a boolean which indicates whether or not 1110 | * the binary already exists. 1111 | */ 1112 | public boolean isBinaryAvailable(Context context, String binaryName) { 1113 | Installer installer; 1114 | 1115 | try { 1116 | installer = new Installer(context); 1117 | } catch (IOException ex) { 1118 | if (RootTools.debugMode) { 1119 | ex.printStackTrace(); 1120 | } 1121 | return false; 1122 | } 1123 | 1124 | return (installer.isBinaryInstalled(binaryName)); 1125 | } 1126 | 1127 | /** 1128 | * This will let you know if an applet is available from BusyBox 1129 | *

1130 | * 1131 | * @param applet The applet to check for. 1132 | * @return true if applet is available, false otherwise. 1133 | */ 1134 | public boolean isAppletAvailable(String applet, String binaryPath) { 1135 | try { 1136 | for (String aplet : getBusyBoxApplets(binaryPath)) { 1137 | if (aplet.equals(applet)) { 1138 | return true; 1139 | } 1140 | } 1141 | return false; 1142 | } catch (Exception e) { 1143 | RootTools.log(e.toString()); 1144 | return false; 1145 | } 1146 | } 1147 | 1148 | /** 1149 | * This method can be used to to check if a process is running 1150 | * 1151 | * @param processName name of process to check 1152 | * @return true if process was found 1153 | * @throws TimeoutException (Could not determine if the process is running) 1154 | */ 1155 | public boolean isProcessRunning(final String processName) { 1156 | 1157 | RootTools.log("Checks if process is running: " + processName); 1158 | 1159 | InternalVariables.processRunning = false; 1160 | 1161 | try { 1162 | Command command = new Command(0, false, "ps | grep " + processName) { 1163 | @Override 1164 | public void commandOutput(int id, String line) { 1165 | if (line.contains(processName)) { 1166 | InternalVariables.processRunning = true; 1167 | } 1168 | 1169 | super.commandOutput(id, line); 1170 | } 1171 | }; 1172 | RootTools.getShell(true).add(command); 1173 | commandWait(RootTools.getShell(true), command); 1174 | 1175 | } catch (Exception e) { 1176 | RootTools.log(e.getMessage()); 1177 | } 1178 | 1179 | return InternalVariables.processRunning; 1180 | } 1181 | 1182 | /** 1183 | * This method can be used to kill a running process 1184 | * 1185 | * @param processName name of process to kill 1186 | * @return true if process was found and killed successfully 1187 | */ 1188 | public boolean killProcess(final String processName) { 1189 | RootTools.log("Killing process " + processName); 1190 | 1191 | InternalVariables.pid_list = ""; 1192 | 1193 | //Assume that the process is running 1194 | InternalVariables.processRunning = true; 1195 | 1196 | try { 1197 | 1198 | Command command = new Command(0, false, "ps") { 1199 | @Override 1200 | public void commandOutput(int id, String line) { 1201 | if (line.contains(processName)) { 1202 | Matcher psMatcher = InternalVariables.psPattern.matcher(line); 1203 | 1204 | try { 1205 | if (psMatcher.find()) { 1206 | String pid = psMatcher.group(1); 1207 | 1208 | InternalVariables.pid_list += " " + pid; 1209 | InternalVariables.pid_list = InternalVariables.pid_list.trim(); 1210 | 1211 | RootTools.log("Found pid: " + pid); 1212 | } else { 1213 | RootTools.log("Matching in ps command failed!"); 1214 | } 1215 | } catch (Exception e) { 1216 | RootTools.log("Error with regex!"); 1217 | e.printStackTrace(); 1218 | } 1219 | } 1220 | 1221 | super.commandOutput(id, line); 1222 | } 1223 | }; 1224 | RootTools.getShell(true).add(command); 1225 | commandWait(RootTools.getShell(true), command); 1226 | 1227 | // get all pids in one string, created in process method 1228 | String pids = InternalVariables.pid_list; 1229 | 1230 | // kill processes 1231 | if (!pids.equals("")) { 1232 | try { 1233 | // example: kill -9 1234 1222 5343 1234 | command = new Command(0, false, "kill -9 " + pids); 1235 | RootTools.getShell(true).add(command); 1236 | commandWait(RootTools.getShell(true), command); 1237 | 1238 | return true; 1239 | } catch (Exception e) { 1240 | RootTools.log(e.getMessage()); 1241 | } 1242 | } else { 1243 | //no pids match, must be dead 1244 | return true; 1245 | } 1246 | } catch (Exception e) { 1247 | RootTools.log(e.getMessage()); 1248 | } 1249 | 1250 | return false; 1251 | } 1252 | 1253 | /** 1254 | * This will launch the Android market looking for BusyBox 1255 | * 1256 | * @param activity pass in your Activity 1257 | */ 1258 | public void offerBusyBox(Activity activity) { 1259 | RootTools.log("Launching Market for BusyBox"); 1260 | Intent i = new Intent(Intent.ACTION_VIEW, 1261 | Uri.parse("market://details?id=stericson.busybox")); 1262 | activity.startActivity(i); 1263 | } 1264 | 1265 | /** 1266 | * This will launch the Android market looking for BusyBox, but will return the intent fired and 1267 | * starts the activity with startActivityForResult 1268 | * 1269 | * @param activity pass in your Activity 1270 | * @param requestCode pass in the request code 1271 | * @return intent fired 1272 | */ 1273 | public Intent offerBusyBox(Activity activity, int requestCode) { 1274 | RootTools.log("Launching Market for BusyBox"); 1275 | Intent i = new Intent(Intent.ACTION_VIEW, 1276 | Uri.parse("market://details?id=stericson.busybox")); 1277 | activity.startActivityForResult(i, requestCode); 1278 | return i; 1279 | } 1280 | 1281 | /** 1282 | * This will launch the Play Store looking for SuperUser 1283 | * 1284 | * @param activity pass in your Activity 1285 | */ 1286 | public void offerSuperUser(Activity activity) { 1287 | RootTools.log("Launching Play Store for SuperSU"); 1288 | Intent i = new Intent(Intent.ACTION_VIEW, 1289 | Uri.parse("market://details?id=eu.chainfire.supersu")); 1290 | activity.startActivity(i); 1291 | } 1292 | 1293 | /** 1294 | * This will launch the Play Store looking for SuperSU, but will return the intent fired 1295 | * and starts the activity with startActivityForResult 1296 | * 1297 | * @param activity pass in your Activity 1298 | * @param requestCode pass in the request code 1299 | * @return intent fired 1300 | */ 1301 | public Intent offerSuperUser(Activity activity, int requestCode) { 1302 | RootTools.log("Launching Play Store for SuperSU"); 1303 | Intent i = new Intent(Intent.ACTION_VIEW, 1304 | Uri.parse("market://details?id=eu.chainfire.supersu")); 1305 | activity.startActivityForResult(i, requestCode); 1306 | return i; 1307 | } 1308 | 1309 | private void commandWait(Shell shell, Command cmd) throws Exception { 1310 | 1311 | while (!cmd.isFinished()) { 1312 | 1313 | RootTools.log(Constants.TAG, shell.getCommandQueuePositionString(cmd)); 1314 | RootTools.log(Constants.TAG, "Processed " + cmd.totalOutputProcessed + " of " + cmd.totalOutput + " output from command."); 1315 | 1316 | synchronized (cmd) { 1317 | try { 1318 | if (!cmd.isFinished()) { 1319 | cmd.wait(2000); 1320 | } 1321 | } catch (InterruptedException e) { 1322 | e.printStackTrace(); 1323 | } 1324 | } 1325 | 1326 | if (!cmd.isExecuting() && !cmd.isFinished()) { 1327 | if (!shell.isExecuting && !shell.isReading) { 1328 | Log.e(Constants.TAG, "Waiting for a command to be executed in a shell that is not executing and not reading! \n\n Command: " + cmd.getCommand()); 1329 | Exception e = new Exception(); 1330 | e.setStackTrace(Thread.currentThread().getStackTrace()); 1331 | e.printStackTrace(); 1332 | } else if (shell.isExecuting && !shell.isReading) { 1333 | Log.e(Constants.TAG, "Waiting for a command to be executed in a shell that is executing but not reading! \n\n Command: " + cmd.getCommand()); 1334 | Exception e = new Exception(); 1335 | e.setStackTrace(Thread.currentThread().getStackTrace()); 1336 | e.printStackTrace(); 1337 | } else { 1338 | Log.e(Constants.TAG, "Waiting for a command to be executed in a shell that is not reading! \n\n Command: " + cmd.getCommand()); 1339 | Exception e = new Exception(); 1340 | e.setStackTrace(Thread.currentThread().getStackTrace()); 1341 | e.printStackTrace(); 1342 | } 1343 | } 1344 | 1345 | } 1346 | } 1347 | } 1348 | -------------------------------------------------------------------------------- /src/main/java/com/stericson/RootTools/internal/Runner.java: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is part of the RootTools Project: http://code.google.com/p/RootTools/ 3 | * 4 | * Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks 5 | * 6 | * This code is dual-licensed under the terms of the Apache License Version 2.0 and 7 | * the terms of the General Public License (GPL) Version 2. 8 | * You may use this code according to either of these licenses as is most appropriate 9 | * for your project on a case-by-case basis. 10 | * 11 | * The terms of each license can be found in the root directory of this project's repository as well as at: 12 | * 13 | * * http://www.apache.org/licenses/LICENSE-2.0 14 | * * http://www.gnu.org/licenses/gpl-2.0.txt 15 | * 16 | * Unless required by applicable law or agreed to in writing, software 17 | * distributed under these Licenses is distributed on an "AS IS" BASIS, 18 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 19 | * See each License for the specific language governing permissions and 20 | * limitations under that License. 21 | */ 22 | 23 | package com.stericson.RootTools.internal; 24 | 25 | import java.io.IOException; 26 | 27 | import com.stericson.RootShell.execution.Command; 28 | import com.stericson.RootShell.execution.Shell; 29 | import com.stericson.RootTools.RootTools; 30 | 31 | import android.content.Context; 32 | import android.util.Log; 33 | 34 | public class Runner extends Thread 35 | { 36 | 37 | private static final String LOG_TAG = "RootTools::Runner"; 38 | 39 | Context context; 40 | String binaryName; 41 | String parameter; 42 | 43 | public Runner(Context context, String binaryName, String parameter) 44 | { 45 | this.context = context; 46 | this.binaryName = binaryName; 47 | this.parameter = parameter; 48 | } 49 | 50 | public void run() 51 | { 52 | String privateFilesPath = null; 53 | try 54 | { 55 | privateFilesPath = context.getFilesDir().getCanonicalPath(); 56 | } 57 | catch (IOException e) 58 | { 59 | if (RootTools.debugMode) 60 | { 61 | Log.e(LOG_TAG, "Problem occured while trying to locate private files directory!"); 62 | } 63 | e.printStackTrace(); 64 | } 65 | if (privateFilesPath != null) 66 | { 67 | try 68 | { 69 | Command command = new Command(0, false, privateFilesPath + "/" + binaryName + " " + parameter); 70 | Shell.startRootShell().add(command); 71 | commandWait(command); 72 | 73 | } 74 | catch (Exception e) 75 | { 76 | } 77 | } 78 | } 79 | 80 | private void commandWait(Command cmd) 81 | { 82 | synchronized (cmd) 83 | { 84 | try 85 | { 86 | if (!cmd.isFinished()) 87 | { 88 | cmd.wait(2000); 89 | } 90 | } 91 | catch (InterruptedException e) 92 | { 93 | e.printStackTrace(); 94 | } 95 | } 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | RootTools 3 | 4 | -------------------------------------------------------------------------------- /src/test/java/com/example/roottools/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.example.RootTools; 2 | 3 | import org.junit.Test; 4 | 5 | import static org.junit.Assert.*; 6 | 7 | /** 8 | * To work on unit tests, switch the Test Artifact in the Build Variants view. 9 | */ 10 | public class ExampleUnitTest { 11 | @Test 12 | public void addition_isCorrect() throws Exception { 13 | assertEquals(4, 2 + 2); 14 | } 15 | } 16 | --------------------------------------------------------------------------------