/*
* This file is part of the RootTools Project: http://code.google.com/p/roottools/
*
* Copyright (c) 2012 Stephen Erickson, Chris Ravenscroft, Dominik Schuermann, Adam Shanks
*
* This code is dual-licensed under the terms of the Apache License Version 2.0 and
* the terms of the General Public License (GPL) Version 2.
* You may use this code according to either of these licenses as is most appropriate
* for your project on a case-by-case basis.
*
* The terms of each license can be found in the root directory of this project's repository as well as at:
*
* * http://www.apache.org/licenses/LICENSE-2.0
* * http://www.gnu.org/licenses/gpl-2.0.txt
*
* Unless required by applicable law or agreed to in writing, software
* distributed under these Licenses is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See each License for the specific language governing permissions and
* limitations under that License.
*/
package com.stericson.RootTools.internal;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeoutException;
import java.util.regex.Matcher;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Environment;
import android.os.StatFs;
import android.util.Log;
import com.stericson.RootTools.Constants;
import com.stericson.RootTools.RootTools;
import com.stericson.RootTools.containers.Mount;
import com.stericson.RootTools.containers.Permissions;
import com.stericson.RootTools.containers.Symlink;
import com.stericson.RootTools.execution.Command;
import com.stericson.RootTools.execution.CommandCapture;
import com.stericson.RootTools.execution.Shell;
public final class RootToolsInternalMethods {
// --------------------
// # Internal methods #
// --------------------
protected RootToolsInternalMethods() {}
public static void getInstance() {
//this will allow RootTools to be the only one to get an instance of this class.
RootTools.setRim(new RootToolsInternalMethods());
}
public ArrayList<Symlink> getSymLinks() throws IOException {
LineNumberReader lnr = null;
FileReader fr = null;
try {
fr = new FileReader("/data/local/symlinks.txt");
lnr = new LineNumberReader(fr);
String line;
ArrayList<Symlink> symlink = new ArrayList<Symlink>();
while ((line = lnr.readLine()) != null) {
RootTools.log(line);
String[] fields = line.split(" ");
symlink.add(new Symlink(new File(fields[fields.length - 3]), // file
new File(fields[fields.length - 1]) // SymlinkPath
));
}
return symlink;
} finally {
try {
fr.close();
} catch (Exception e) {}
try {
lnr.close();
} catch (Exception e) {}
}
}
public Permissions getPermissions(String line) {
String[] lineArray = line.split(" ");
String rawPermissions = lineArray[0];
if (rawPermissions.length() == 10
&& (rawPermissions.charAt(0) == '-'
|| rawPermissions.charAt(0) == 'd' || rawPermissions
.charAt(0) == 'l')
&& (rawPermissions.charAt(1) == '-' || rawPermissions.charAt(1) == 'r')
&& (rawPermissions.charAt(2) == '-' || rawPermissions.charAt(2) == 'w')) {
RootTools.log(rawPermissions);
Permissions permissions = new Permissions();
permissions.setType(rawPermissions.substring(0, 1));
RootTools.log(permissions.getType());
permissions.setUserPermissions(rawPermissions.substring(1, 4));
RootTools.log(permissions.getUserPermissions());
permissions.setGroupPermissions(rawPermissions.substring(4, 7));
RootTools.log(permissions.getGroupPermissions());
permissions.setOtherPermissions(rawPermissions.substring(7, 10));
RootTools.log(permissions.getOtherPermissions());
StringBuilder finalPermissions = new StringBuilder();
finalPermissions.append(parseSpecialPermissions(rawPermissions));
finalPermissions.append(parsePermissions(permissions.getUserPermissions()));
finalPermissions.append(parsePermissions(permissions.getGroupPermissions()));
finalPermissions.append(parsePermissions(permissions.getOtherPermissions()));
permissions.setPermissions(Integer.parseInt(finalPermissions.toString()));
return permissions;
}
return null;
}
public int parsePermissions(String permission) {
int tmp;
if (permission.charAt(0) == 'r')
tmp = 4;
else
tmp = 0;
RootTools.log("permission " + tmp);
RootTools.log("character " + permission.charAt(0));
if (permission.charAt(1) == 'w')
tmp += 2;
else
tmp += 0;
RootTools.log("permission " + tmp);
RootTools.log("character " + permission.charAt(1));
if (permission.charAt(2) == 'x')
tmp += 1;
else
tmp += 0;
RootTools.log("permission " + tmp);
RootTools.log("character " + permission.charAt(2));
return tmp;
}
public int parseSpecialPermissions(String permission) {
int tmp = 0;
if (permission.charAt(2) == 's')
tmp += 4;
if (permission.charAt(5) == 's')
tmp += 2;
if (permission.charAt(8) == 't')
tmp += 1;
RootTools.log("special permissions " + tmp);
return tmp;
}
/**
* Copys a file to a destination. Because cp is not available on all android devices, we have a
* fallback on the cat command
*
* @param source example: /data/data/org.adaway/files/hosts
* @param destination example: /system/etc/hosts
* @param remountAsRw remounts the destination as read/write before writing to it
* @param preserveFileAttributes tries to copy file attributes from source to destination, if only cat is available
* only permissions are preserved
* @return true if it was successfully copied
*/
public boolean copyFile(String source, String destination, boolean remountAsRw,
boolean preserveFileAttributes) {
CommandCapture command = null;
boolean result = true;
try {
// mount destination as rw before writing to it
if (remountAsRw) {
RootTools.remount(destination, "RW");
}
// if cp is available and has appropriate permissions
if (checkUtil("cp")) {
RootTools.log("cp command is available!");
if (preserveFileAttributes) {
command = new CommandCapture(0, false, "cp -fp " + source + " " + destination);
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
//ensure that the file was copied, an exitcode of zero means success
result = command.getExitCode() == 0;
} else {
command = new CommandCapture(0, false, "cp -f " + source + " " + destination);
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
//ensure that the file was copied, an exitcode of zero means success
result = command.getExitCode() == 0;
}
} else {
if (checkUtil("busybox") && hasUtil("cp", "busybox")) {
RootTools.log("busybox cp command is available!");
if (preserveFileAttributes) {
command = new CommandCapture(0, false, "busybox cp -fp " + source + " " + destination);
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
} else {
command = new CommandCapture(0, false, "busybox cp -f " + source + " " + destination);
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
}
} else { // if cp is not available use cat
// if cat is available and has appropriate permissions
if (checkUtil("cat")) {
RootTools.log("cp is not available, use cat!");
int filePermission = -1;
if (preserveFileAttributes) {
// get permissions of source before overwriting
Permissions permissions = getFilePermissionsSymlinks(source);
filePermission = permissions.getPermissions();
}
// copy with cat
command = new CommandCapture(0, false, "cat " + source + " > " + destination);
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
if (preserveFileAttributes) {
// set premissions of source to destination
command = new CommandCapture(0, false, "chmod " + filePermission + " " + destination);
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
}
} else {
result = false;
}
}
}
// mount destination back to ro
if (remountAsRw) {
RootTools.remount(destination, "RO");
}
} catch (Exception e) {
e.printStackTrace();
result = false;
}
if (command != null) {
//ensure that the file was copied, an exitcode of zero means success
result = command.getExitCode() == 0;
}
return result;
}
/**
* This will check a given binary, determine if it exists and determine that
* it has either the permissions 755, 775, or 777.
*
* @param util Name of the utility to check.
* @return boolean to indicate whether the binary is installed and has
* appropriate permissions.
*/
public boolean checkUtil(String util) {
if (RootTools.findBinary(util)) {
List<String> binaryPaths = new ArrayList<String>();
binaryPaths.addAll(RootTools.lastFoundBinaryPaths);
for (String path : binaryPaths) {
Permissions permissions = RootTools
.getFilePermissionsSymlinks(path + "/" + util);
if (permissions != null) {
String permission;
if (Integer.toString(permissions.getPermissions()).length() > 3)
permission = Integer.toString(permissions.getPermissions()).substring(1);
else
permission = Integer.toString(permissions.getPermissions());
if (permission.equals("755") || permission.equals("777")
|| permission.equals("775")) {
RootTools.utilPath = path + "/" + util;
return true;
}
}
}
}
return false;
}
/**
* Deletes a file or directory
*
* @param target example: /data/data/org.adaway/files/hosts
* @param remountAsRw remounts the destination as read/write before writing to it
* @return true if it was successfully deleted
*/
public boolean deleteFileOrDirectory(String target, boolean remountAsRw) {
boolean result = true;
try {
// mount destination as rw before writing to it
if (remountAsRw) {
RootTools.remount(target, "RW");
}
if (hasUtil("rm", "toolbox")) {
RootTools.log("rm command is available!");
CommandCapture command = new CommandCapture(0, false, "rm -r " + target);
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
if (command.getExitCode() != 0) {
RootTools.log("target not exist or unable to delete file");
result = false;
}
} else {
if (checkUtil("busybox") && hasUtil("rm", "busybox")) {
RootTools.log("busybox rm command is available!");
CommandCapture command = new CommandCapture(0, false, "busybox rm -rf " + target);
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
if (command.getExitCode() != 0) {
RootTools.log("target not exist or unable to delete file");
result = false;
}
}
}
// mount destination back to ro
if (remountAsRw) {
RootTools.remount(target, "RO");
}
} catch (Exception e) {
e.printStackTrace();
result = false;
}
return result;
}
/**
* Use this to check whether or not a file exists on the filesystem.
*
* @param file String that represent the file, including the full path to the
* file and its name.
* @return a boolean that will indicate whether or not the file exists.
*/
public boolean exists(final String file) {
return exists(file, false);
}
/**
* Use this to check whether or not a file OR directory exists on the filesystem.
*
* @param file String that represent the file OR the directory, including the full path to the
* file and its name.
*
* @param isDir boolean that represent whether or not we are looking for a directory
*
* @return a boolean that will indicate whether or not the file exists.
*/
public boolean exists(final String file, boolean isDir) {
final List<String> result = new ArrayList<String>();
String cmdToExecute = "ls " + (isDir ? "-d " : " ");
CommandCapture command = new CommandCapture(0, false, cmdToExecute + file) {
@Override
public void output(int arg0, String arg1) {
RootTools.log(arg1);
result.add(arg1);
}
};
try {
//Try without root...
Shell.startShell().add(command);
commandWait(Shell.startShell(), command);
} catch (Exception e) {
return false;
}
for (String line : result) {
if (line.trim().equals(file)) {
return true;
}
}
try {
RootTools.closeShell(false);
} catch (Exception e) {}
result.clear();
try {
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
} catch (Exception e) {
return false;
}
//Avoid concurrent modification...
List<String> final_result = new ArrayList<String>();
final_result.addAll(result);
for (String line : final_result) {
if (line.trim().equals(file)) {
return true;
}
}
return false;
}
/**
* This will try and fix a given binary. (This is for Busybox applets or Toolbox applets) By
* "fix", I mean it will try and symlink the binary from either toolbox or Busybox and fix the
* permissions if the permissions are not correct.
*
* @param util Name of the utility to fix.
* @param utilPath path to the toolbox that provides ln, rm, and chmod. This can be a blank string, a
* path to a binary that will provide these, or you can use
* RootTools.getWorkingToolbox()
*/
public void fixUtil(String util, String utilPath) {
try {
RootTools.remount("/system", "rw");
if (RootTools.findBinary(util)) {
List<String> paths = new ArrayList<String>();
paths.addAll(RootTools.lastFoundBinaryPaths);
for (String path : paths) {
CommandCapture command = new CommandCapture(0, false, utilPath + " rm " + path + "/" + util);
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
}
CommandCapture command = new CommandCapture(0, false, utilPath + " ln -s " + utilPath + " /system/bin/" + util, utilPath + " chmod 0755 /system/bin/" + util);
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
}
RootTools.remount("/system", "ro");
} catch (Exception e) {
}
}
/**
* This will check an array of binaries, determine if they exist and determine that it has
* either the permissions 755, 775, or 777. If an applet is not setup correctly it will try and
* fix it. (This is for Busybox applets or Toolbox applets)
*
* @param utils Name of the utility to check.
* @return boolean to indicate whether the operation completed. Note that this is not indicative
* of whether the problem was fixed, just that the method did not encounter any
* exceptions.
* @throws Exception if the operation cannot be completed.
*/
public boolean fixUtils(String[] utils) throws Exception {
for (String util : utils) {
if (!checkUtil(util)) {
if (checkUtil("busybox")) {
if (hasUtil(util, "busybox")) {
fixUtil(util, RootTools.utilPath);
}
} else {
if (checkUtil("toolbox")) {
if (hasUtil(util, "toolbox")) {
fixUtil(util, RootTools.utilPath);
}
} else {
return false;
}
}
}
}
return true;
}
/**
* @param binaryName String that represent the binary to find.
* @return <code>true</code> if the specified binary was found. Also, the path the binary was
* found at can be retrieved via the variable lastFoundBinaryPath, if the binary was
* found in more than one location this will contain all of these locations.
*/
public boolean findBinary(final String binaryName) {
boolean found = false;
RootTools.lastFoundBinaryPaths.clear();
final List<String> list = new ArrayList<String>();
String[] places = {"/sbin/", "/system/bin/", "/system/xbin/", "/data/local/xbin/",
"/data/local/bin/", "/system/sd/xbin/", "/system/bin/failsafe/", "/data/local/"};
RootTools.log("Checking for " + binaryName);
//Try to use stat first
try {
for(final String path : places) {
CommandCapture cc = new CommandCapture(0, false, "stat " + path + binaryName) {
@Override
public void commandOutput(int id, String line) {
if(line.contains("File: ") && line.contains(binaryName)) {
list.add(path);
RootTools.log(binaryName + " was found here: " + path);
}
RootTools.log(line);
}
};
RootTools.getShell(false).add(cc);
commandWait(RootTools.getShell(false), cc);
}
found = !list.isEmpty();
} catch (Exception e) {
RootTools.log(binaryName + " was not found, more information MAY be available with Debugging on.");
}
if (!found) {
RootTools.log("Trying second method");
for (String where : places) {
if (RootTools.exists(where + binaryName)) {
RootTools.log(binaryName + " was found here: " + where);
list.add(where);
found = true;
} else {
RootTools.log(binaryName + " was NOT found here: " + where);
}
}
}
if(!found) {
RootTools.log("Trying third method");
try {
List<String> paths = RootTools.getPath();
if (paths != null) {
for (String path : paths) {
if (RootTools.exists(path + "/" + binaryName)) {
RootTools.log(binaryName + " was found here: " + path);
list.add(path);
found = true;
} else {
RootTools.log(binaryName + " was NOT found here: " + path);
}
}
}
} catch (Exception e) {
RootTools.log(binaryName + " was not found, more information MAY be available with Debugging on.");
}
}
Collections.reverse(list);
RootTools.lastFoundBinaryPaths.addAll(list);
return found;
}
/**
* This will return an List of Strings. Each string represents an applet available from BusyBox.
* <p/>
*
* @param path Path to the busybox binary that you want the list of applets from.
* @return <code>null</code> If we cannot return the list of applets.
*/
public List<String> getBusyBoxApplets(String path) throws Exception {
if (path != null && !path.endsWith("/") && !path.equals("")) {
path += "/";
} else if (path == null) {
//Don't know what the user wants to do...what am I pshycic?
throw new Exception("Path is null, please specifiy a path");
}
final List<String> results = new ArrayList<String>();
CommandCapture command = new CommandCapture(Constants.BBA, false, path + "busybox --list") {
@Override
public void output(int id, String line) {
if (id == Constants.BBA) {
if (!line.trim().equals("") && !line.trim().contains("not found")) {
results.add(line);
}
}
}
};
//try without root first...
Shell.startShell().add(command);
commandWait(Shell.startShell(), command);
if(results.size() <= 0) {
//try with root...
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
}
return results;
}
/**
* @return BusyBox version if found, "" if not found.
*/
public String getBusyBoxVersion(String path) {
if (!path.equals("") && !path.endsWith("/")) {
path += "/";
}
InternalVariables.busyboxVersion = "";
try {
CommandCapture command = new CommandCapture(Constants.BBV, false, path + "busybox") {
@Override
public void output(int id, String line) {
if (id == Constants.BBV) {
if (line.startsWith("BusyBox") && InternalVariables.busyboxVersion.equals("")) {
String[] temp = line.split(" ");
InternalVariables.busyboxVersion = temp[1];
}
}
}
};
//try without root first
RootTools.log("Getting BusyBox Version without root");
Shell.startShell().add(command);
commandWait(Shell.startShell(), command);
if(InternalVariables.busyboxVersion.length() <= 0)
{
//try without root first
RootTools.log("Getting BusyBox Version with root");
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
}
} catch (Exception e) {
RootTools.log("BusyBox was not found, more information MAY be available with Debugging on.");
return "";
}
return InternalVariables.busyboxVersion;
}
/**
* @return long Size, converted to kilobytes (from xxx or xxxm or xxxk etc.)
*/
public long getConvertedSpace(String spaceStr) {
try {
double multiplier = 1.0;
char c;
StringBuffer sb = new StringBuffer();
for (int i = 0; i < spaceStr.length(); i++) {
c = spaceStr.charAt(i);
if (!Character.isDigit(c) && c != '.') {
if (c == 'm' || c == 'M') {
multiplier = 1024.0;
} else if (c == 'g' || c == 'G') {
multiplier = 1024.0 * 1024.0;
}
break;
}
sb.append(spaceStr.charAt(i));
}
return (long) Math.ceil(Double.valueOf(sb.toString()) * multiplier);
} catch (Exception e) {
return -1;
}
}
/**
* This method will return the inode number of a file. This method is dependent on having a version of
* ls that supports the -i parameter.
*
* @param file path to the file that you wish to return the inode number
* @return String The inode number for this file or "" if the inode number could not be found.
*/
public String getInode(String file) {
try {
CommandCapture command = new CommandCapture(Constants.GI, false, "/data/local/ls -i " + file) {
@Override
public void output(int id, String line) {
if (id == Constants.GI) {
if (!line.trim().equals("") && Character.isDigit((char) line.trim().substring(0, 1).toCharArray()[0])) {
InternalVariables.inode = line.trim().split(" ")[0];
}
}
}
};
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
return InternalVariables.inode;
} catch (Exception ignore) {
return "";
}
}
/**
* @return <code>true</code> if your app has been given root access.
* @throws TimeoutException if this operation times out. (cannot determine if access is given)
*/
public boolean isAccessGiven() {
try {
RootTools.log("Checking for Root access");
InternalVariables.accessGiven = false;
CommandCapture command = new CommandCapture(Constants.IAG, false, "id") {
@Override
public void output(int id, String line) {
if (id == Constants.IAG) {
Set<String> ID = new HashSet<String>(Arrays.asList(line.split(" ")));
for (String userid : ID) {
RootTools.log(userid);
if (userid.toLowerCase().contains("uid=0")) {
InternalVariables.accessGiven = true;
RootTools.log("Access Given");
break;
}
}
if (!InternalVariables.accessGiven) {
RootTools.log("Access Denied?");
}
}
}
};
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
return InternalVariables.accessGiven;
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
public boolean isNativeToolsReady(int nativeToolsId, Context context) {
RootTools.log("Preparing Native Tools");
InternalVariables.nativeToolsReady = false;
Installer installer;
try {
installer = new Installer(context);
} catch (IOException ex) {
if (RootTools.debugMode) {
ex.printStackTrace();
}
return false;
}
if (installer.isBinaryInstalled("nativetools")) {
InternalVariables.nativeToolsReady = true;
} else {
InternalVariables.nativeToolsReady = installer.installBinary(nativeToolsId,
"nativetools", "700");
}
return InternalVariables.nativeToolsReady;
}
/**
* @param file String that represent the file, including the full path to the
* file and its name.
* @return An instance of the class permissions from which you can get the
* permissions of the file or if the file could not be found or
* permissions couldn't be determined then permissions will be null.
*/
public Permissions getFilePermissionsSymlinks(String file) {
RootTools.log("Checking permissions for " + file);
if (RootTools.exists(file)) {
RootTools.log(file + " was found.");
try {
CommandCapture command = new CommandCapture(
Constants.FPS, false, "ls -l " + file,
"busybox ls -l " + file,
"/system/bin/failsafe/toolbox ls -l " + file,
"toolbox ls -l " + file) {
@Override
public void output(int id, String line) {
if (id == Constants.FPS) {
String symlink_final = "";
String[] lineArray = line.split(" ");
if (lineArray[0].length() != 10) {
return;
}
RootTools.log("Line " + line);
try {
String[] symlink = line.split(" ");
if (symlink[symlink.length - 2].equals("->")) {
RootTools.log("Symlink found.");
symlink_final = symlink[symlink.length - 1];
}
} catch (Exception e) {}
try {
InternalVariables.permissions = getPermissions(line);
if (InternalVariables.permissions != null) {
InternalVariables.permissions.setSymlink(symlink_final);
}
} catch (Exception e) {
RootTools.log(e.getMessage());
}
}
}
};
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
return InternalVariables.permissions;
} catch (Exception e) {
RootTools.log(e.getMessage());
return null;
}
}
return null;
}
/**
* This will return an ArrayList of the class Mount. The class mount contains the following
* property's: device mountPoint type flags
* <p/>
* These will provide you with any information you need to work with the mount points.
*
* @return <code>ArrayList<Mount></code> an ArrayList of the class Mount.
* @throws Exception if we cannot return the mount points.
*/
public ArrayList<Mount> getMounts() throws Exception {
Shell shell = RootTools.getShell(true);
CommandCapture cmd = new CommandCapture(0,
false,
"cat /proc/mounts > /data/local/RootToolsMounts",
"chmod 0777 /data/local/RootToolsMounts");
shell.add(cmd);
this.commandWait(shell, cmd);
LineNumberReader lnr = null;
FileReader fr = null;
try {
fr = new FileReader("/data/local/RootToolsMounts");
lnr = new LineNumberReader(fr);
String line;
ArrayList<Mount> mounts = new ArrayList<Mount>();
while ((line = lnr.readLine()) != null) {
RootTools.log(line);
String[] fields = line.split(" ");
mounts.add(new Mount(new File(fields[0]), // device
new File(fields[1]), // mountPoint
fields[2], // fstype
fields[3] // flags
));
}
InternalVariables.mounts = mounts;
if (InternalVariables.mounts != null) {
return InternalVariables.mounts;
} else {
throw new Exception();
}
} finally {
try {
fr.close();
} catch (Exception e) {}
try {
lnr.close();
} catch (Exception e) {}
}
}
/**
* This will tell you how the specified mount is mounted. rw, ro, etc...
* <p/>
*
* @param path mount you want to check
* @return <code>String</code> What the mount is mounted as.
* @throws Exception if we cannot determine how the mount is mounted.
*/
public String getMountedAs(String path) throws Exception {
InternalVariables.mounts = getMounts();
String mp;
if (InternalVariables.mounts != null) {
for (Mount mount : InternalVariables.mounts) {
mp = mount.getMountPoint().getAbsolutePath();
if (mp.equals("/")) {
if (path.equals("/")) {
return (String) mount.getFlags().toArray()[0];
}
else {
continue;
}
}
if (path.equals(mp) || path.startsWith(mp + "/")) {
RootTools.log((String) mount.getFlags().toArray()[0]);
return (String) mount.getFlags().toArray()[0];
}
}
throw new Exception();
} else {
throw new Exception();
}
}
/**
* Get the space for a desired partition.
*
* @param path The partition to find the space for.
* @return the amount if space found within the desired partition. If the space was not found
* then the value is -1
* @throws TimeoutException
*/
public long getSpace(String path) {
InternalVariables.getSpaceFor = path;
boolean found = false;
RootTools.log("Looking for Space");
try {
final CommandCapture command = new CommandCapture(Constants.GS, false, "df " + path) {
@Override
public void output(int id, String line) {
if (id == Constants.GS) {
if (line.contains(InternalVariables.getSpaceFor.trim())) {
InternalVariables.space = line.split(" ");
}
}
}
};
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
} catch (Exception e) {}
if (InternalVariables.space != null) {
RootTools.log("First Method");
for (String spaceSearch : InternalVariables.space) {
RootTools.log(spaceSearch);
if (found) {
return getConvertedSpace(spaceSearch);
} else if (spaceSearch.equals("used,")) {
found = true;
}
}
// Try this way
int count = 0, targetCount = 3;
RootTools.log("Second Method");
if (InternalVariables.space[0].length() <= 5) {
targetCount = 2;
}
for (String spaceSearch : InternalVariables.space) {
RootTools.log(spaceSearch);
if (spaceSearch.length() > 0) {
RootTools.log(spaceSearch + ("Valid"));
if (count == targetCount) {
return getConvertedSpace(spaceSearch);
}
count++;
}
}
}
RootTools.log("Returning -1, space could not be determined.");
return -1;
}
/**
* This will return a String that represent the symlink for a specified file.
* <p/>
*
* @param file file to get the Symlink for. (must have absolute path)
* @return <code>String</code> a String that represent the symlink for a specified file or an
* empty string if no symlink exists.
*/
public String getSymlink(String file) {
RootTools.log("Looking for Symlink for " + file);
try {
final List<String> results = new ArrayList<String>();
CommandCapture command = new CommandCapture(Constants.GSYM, false, "ls -l " + file) {
@Override
public void output(int id, String line) {
if (id == Constants.GSYM) {
if (!line.trim().equals("")) {
results.add(line);
}
}
}
};
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
String[] symlink = results.get(0).split(" ");
if (symlink.length > 2 && symlink[symlink.length - 2].equals("->")) {
RootTools.log("Symlink found.");
String final_symlink;
if (!symlink[symlink.length - 1].equals("") && !symlink[symlink.length - 1].contains("/")) {
//We assume that we need to get the path for this symlink as it is probably not absolute.
findBinary(symlink[symlink.length - 1]);
if (RootTools.lastFoundBinaryPaths.size() > 0) {
//We return the first found location.
final_symlink = RootTools.lastFoundBinaryPaths.get(0) + "/" + symlink[symlink.length - 1];
} else {
//we couldnt find a path, return the symlink by itself.
final_symlink = symlink[symlink.length - 1];
}
} else {
final_symlink = symlink[symlink.length - 1];
}
return final_symlink;
}
} catch (Exception e) {
if (RootTools.debugMode)
e.printStackTrace();
}
RootTools.log("Symlink not found");
return "";
}
/**
* This will return an ArrayList of the class Symlink. The class Symlink contains the following
* property's: path SymplinkPath
* <p/>
* These will provide you with any Symlinks in the given path.
*
* @param path path to search for Symlinks.
* @return <code>ArrayList<Symlink></code> an ArrayList of the class Symlink.
* @throws Exception if we cannot return the Symlinks.
*/
public ArrayList<Symlink> getSymlinks(String path) throws Exception {
// this command needs find
if (!checkUtil("find")) {
throw new Exception();
}
CommandCapture command = new CommandCapture(0, false, "dd if=/dev/zero of=/data/local/symlinks.txt bs=1024 count=1", "chmod 0777 /data/local/symlinks.txt");
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
command = new CommandCapture(0, false, "find " + path + " -type l -exec ls -l {} \\; > /data/local/symlinks.txt");
Shell.startRootShell().add(command);
commandWait(Shell.startRootShell(), command);
InternalVariables.symlinks = getSymLinks();
if (InternalVariables.symlinks != null) {
return InternalVariables.symlinks;
} else {
throw new Exception();
}
}
/**
* This will return to you a string to be used in your shell commands which will represent the
* valid working toolbox with correct permissions. For instance, if Busybox is available it will
* return "busybox", if busybox is not available but toolbox is then it will return "toolbox"
*
* @return String that indicates the available toolbox to use for accessing applets.
*/
public String getWorkingToolbox() {
if (RootTools.checkUtil("busybox")) {
return "busybox";
} else if (RootTools.checkUtil("toolbox")) {
return "toolbox";
} else {
return "";
}
}
/**
* Checks if there is enough Space on SDCard
*
* @param updateSize size to Check (long)
* @return <code>true</code> if the Update will fit on SDCard, <code>false</code> if not enough
* space on SDCard. Will also return <code>false</code>, if the SDCard is not mounted as
* read/write
*/
public boolean hasEnoughSpaceOnSdCard(long updateSize) {
RootTools.log("Checking SDcard size and that it is mounted as RW");
String status = Environment.getExternalStorageState();
if (!status.equals(Environment.MEDIA_MOUNTED)) {
return false;
}
File path = Environment.getExternalStorageDirectory();
StatFs stat = new StatFs(path.getPath());
long blockSize = stat.getBlockSize();
long availableBlocks = stat.getAvailableBlocks();
return (updateSize < availableBlocks * blockSize);
}
/**
* Checks whether the toolbox or busybox binary contains a specific util
*
* @param util
* @param box Should contain "toolbox" or "busybox"
* @return true if it contains this util
*/
public boolean hasUtil(final String util, final String box) {
InternalVariables.found = false;
// only for busybox and toolbox
if (!(box.endsWith("toolbox") || box.endsWith("busybox"))) {
return false;
}
try {
CommandCapture command = new CommandCapture(0, false, box.endsWith("toolbox") ? box + " " + util : box + " --list") {
@Override
public void output(int id, String line) {
if (box.endsWith("toolbox")) {
if (!line.contains("no such tool")) {
InternalVariables.found = true;
}
} else if (box.endsWith("busybox")) {
// go through all lines of busybox --list
if (line.contains(util)) {
RootTools.log("Found util!");
InternalVariables.found = true;
}
}
}
};
RootTools.getShell(true).add(command);
commandWait(RootTools.getShell(true), command);
if (InternalVariables.found) {
RootTools.log("Box contains " + util + " util!");
return true;
} else {
RootTools.log("Box does not contain " + util + " util!");
return false;
}
} catch (Exception e) {
RootTools.log(e.getMessage());
return false;
}
}
/**
* This method can be used to unpack a binary from the raw resources folder and store it in
* /data/data/app.package/files/ This is typically useful if you provide your own C- or
* C++-based binary. This binary can then be executed using sendShell() and its full path.
*
* @param context the current activity's <code>Context</code>
* @param sourceId resource id; typically <code>R.raw.id</code>
* @param destName destination file name; appended to /data/data/app.package/files/
* @param mode chmod value for this file
* @return a <code>boolean</code> which indicates whether or not we were able to create the new
* file.
*/
public boolean installBinary(Context context, int sourceId, String destName, String mode) {
Installer installer;
try {
installer = new Installer(context);
} catch (IOException ex) {
if (RootTools.debugMode) {
ex.printStackTrace();
}
return false;
}
return (installer.installBinary(sourceId, destName, mode));
}
/**
* This method checks whether a binary is installed.
*
* @param context the current activity's <code>Context</code>
* @param binaryName binary file name; appended to /data/data/app.package/files/
* @return a <code>boolean</code> which indicates whether or not
* the binary already exists.
*/
public boolean isBinaryAvailable(Context context, String binaryName) {
Installer installer;
try {
installer = new Installer(context);
} catch (IOException ex) {
if (RootTools.debugMode) {
ex.printStackTrace();
}
return false;
}
return (installer.isBinaryInstalled(binaryName));
}
/**
* This will let you know if an applet is available from BusyBox
* <p/>
*
* @param applet The applet to check for.
* @return <code>true</code> if applet is available, false otherwise.
*/
public boolean isAppletAvailable(String applet, String binaryPath) {
try {
for (String aplet : getBusyBoxApplets(binaryPath)) {
if (aplet.equals(applet)) {
return true;
}
}
return false;
} catch (Exception e) {
RootTools.log(e.toString());
return false;
}
}
/**
* This method can be used to to check if a process is running
*
* @param processName name of process to check
* @return <code>true</code> if process was found
* @throws TimeoutException (Could not determine if the process is running)
*/
public boolean isProcessRunning(final String processName) {
RootTools.log("Checks if process is running: " + processName);
InternalVariables.processRunning = false;
try {
CommandCapture command = new CommandCapture(0, false, "ps") {
@Override
public void output(int id, String line) {
if (line.contains(processName)) {
InternalVariables.processRunning = true;
}
}
};
RootTools.getShell(true).add(command);
commandWait(RootTools.getShell(true), command);
} catch (Exception e) {
RootTools.log(e.getMessage());
}
return InternalVariables.processRunning;
}
/**
* This method can be used to kill a running process
*
* @param processName name of process to kill
* @return <code>true</code> if process was found and killed successfully
*/
public boolean killProcess(final String processName) {
RootTools.log("Killing process " + processName);
InternalVariables.pid_list = "";
//Assume that the process is running
InternalVariables.processRunning = true;
try {
CommandCapture command = new CommandCapture(0, false, "ps") {
@Override
public void output(int id, String line) {
if (line.contains(processName)) {
Matcher psMatcher = InternalVariables.psPattern.matcher(line);
try {
if (psMatcher.find()) {
String pid = psMatcher.group(1);
InternalVariables.pid_list += " " + pid;
InternalVariables.pid_list = InternalVariables.pid_list.trim();
RootTools.log("Found pid: " + pid);
} else {
RootTools.log("Matching in ps command failed!");
}
} catch (Exception e) {
RootTools.log("Error with regex!");
e.printStackTrace();
}
}
}
};
RootTools.getShell(true).add(command);
commandWait(RootTools.getShell(true), command);
// get all pids in one string, created in process method
String pids = InternalVariables.pid_list;
// kill processes
if (!pids.equals("")) {
try {
// example: kill -9 1234 1222 5343
command = new CommandCapture(0, false, "kill -9 " + pids);
RootTools.getShell(true).add(command);
commandWait(RootTools.getShell(true), command);
return true;
} catch (Exception e) {
RootTools.log(e.getMessage());
}
} else {
//no pids match, must be dead
return true;
}
} catch (Exception e) {
RootTools.log(e.getMessage());
}
return false;
}
/**
* This will launch the Android market looking for BusyBox
*
* @param activity pass in your Activity
*/
public void offerBusyBox(Activity activity) {
RootTools.log("Launching Market for BusyBox");
Intent i = new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=stericson.busybox"));
activity.startActivity(i);
}
/**
* This will launch the Android market looking for BusyBox, but will return the intent fired and
* starts the activity with startActivityForResult
*
* @param activity pass in your Activity
* @param requestCode pass in the request code
* @return intent fired
*/
public Intent offerBusyBox(Activity activity, int requestCode) {
RootTools.log("Launching Market for BusyBox");
Intent i = new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=stericson.busybox"));
activity.startActivityForResult(i, requestCode);
return i;
}
/**
* This will launch the Android market looking for SuperUser
*
* @param activity pass in your Activity
*/
public void offerSuperUser(Activity activity) {
RootTools.log("Launching Market for SuperUser");
Intent i = new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=com.noshufou.android.su"));
activity.startActivity(i);
}
/**
* This will launch the Android market looking for SuperUser, but will return the intent fired
* and starts the activity with startActivityForResult
*
* @param activity pass in your Activity
* @param requestCode pass in the request code
* @return intent fired
*/
public Intent offerSuperUser(Activity activity, int requestCode) {
RootTools.log("Launching Market for SuperUser");
Intent i = new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=com.noshufou.android.su"));
activity.startActivityForResult(i, requestCode);
return i;
}
private void commandWait(Shell shell, Command cmd) throws Exception {
while (!cmd.isFinished()) {
RootTools.log(Constants.TAG, shell.getCommandQueuePositionString(cmd));
synchronized (cmd) {
try {
if (!cmd.isFinished()) {
cmd.wait(2000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (!cmd.isExecuting() && !cmd.isFinished()) {
if (!shell.isExecuting && !shell.isReading) {
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());
Exception e = new Exception();
e.setStackTrace(Thread.currentThread().getStackTrace());
e.printStackTrace();
} else if (shell.isExecuting && !shell.isReading) {
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());
Exception e = new Exception();
e.setStackTrace(Thread.currentThread().getStackTrace());
e.printStackTrace();
} else {
Log.e(Constants.TAG, "Waiting for a command to be executed in a shell that is not reading! \n\n Command: " + cmd.getCommand());
Exception e = new Exception();
e.setStackTrace(Thread.currentThread().getStackTrace());
e.printStackTrace();
}
}
}
}
}