├── .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 |
--------------------------------------------------------------------------------