Rewrite all root method with own su library

This commit is contained in:
topjohnwu 2016-08-25 05:58:15 +08:00
parent e18f4c843a
commit a5ea214553
10 changed files with 411 additions and 237 deletions

View File

@ -25,7 +25,6 @@ dependencies {
compile 'com.android.support:recyclerview-v7:24.2.0'
compile 'com.android.support:cardview-v7:24.2.0'
compile 'com.android.support:design:24.2.0'
compile 'eu.chainfire:libsuperuser:1.0.0.201607041850'
compile 'com.jakewharton:butterknife:8.2.1'
apt 'com.jakewharton:butterknife-compiler:8.2.1'

View File

@ -25,16 +25,15 @@ import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.WelcomeActivity;
import com.topjohnwu.magisk.utils.Utils;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Calendar;
import java.util.List;
import java.util.concurrent.ExecutionException;
import butterknife.BindView;
@ -42,7 +41,7 @@ import butterknife.ButterKnife;
public class LogFragment extends Fragment {
private static final File MAGISK_LOG = new File("/cache/magisk.log");
private static final String MAGISK_LOG = "/cache/magisk.log";
@BindView(R.id.txtLog) TextView txtLog;
@BindView(R.id.svLog) ScrollView svLog;
@ -100,7 +99,7 @@ public class LogFragment extends Fragment {
}
private void reloadErrorLog() {
new LogsReader().execute(MAGISK_LOG);
new LogsManager(true).execute();
svLog.post(new Runnable() {
@Override
public void run() {
@ -116,17 +115,8 @@ public class LogFragment extends Fragment {
}
private void clear() {
try {
new FileOutputStream(MAGISK_LOG).close();
MAGISK_LOG.delete();
txtLog.setText(R.string.log_is_empty);
Snackbar.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
reloadErrorLog();
} catch (IOException e) {
Toast.makeText(getActivity(), getResources().getString(R.string.logs_clear_failed) + "\n" + e.getMessage(), Toast.LENGTH_LONG).show();
}
new LogsManager(false).execute();
reloadErrorLog();
}
private void send() {
@ -179,16 +169,13 @@ public class LogFragment extends Fragment {
dir.mkdir();
File targetFile = new File(dir, filename);
List<String> in = Utils.readFile(MAGISK_LOG);
try {
FileInputStream in = new FileInputStream(MAGISK_LOG);
FileOutputStream out = new FileOutputStream(targetFile);
byte[] buffer = new byte[1024];
int len;
while ((len = in.read(buffer)) > 0) {
out.write(buffer, 0, len);
FileWriter out = new FileWriter(targetFile);
for (String line : in) {
out.write(line + "\n");
}
in.close();
out.close();
Toast.makeText(getActivity(), targetFile.toString(), Toast.LENGTH_LONG).show();
@ -199,30 +186,12 @@ public class LogFragment extends Fragment {
}
}
private class LogsReader extends AsyncTask<File, Integer, String> {
private class LogsManager extends AsyncTask<Void, Integer, String> {
private static final int MAX_LOG_SIZE = 1000 * 1024; // 1000 KB
private long skipLargeFile(BufferedReader is, long length) throws IOException {
if (length < MAX_LOG_SIZE)
return 0;
long skipped = length - MAX_LOG_SIZE;
long yetToSkip = skipped;
do {
yetToSkip -= is.skip(yetToSkip);
} while (yetToSkip > 0);
int c;
do {
c = is.read();
if (c == -1)
break;
skipped++;
} while (c != '\n');
return skipped;
private boolean readLog;
public LogsManager(boolean read) {
readLog = read;
}
@Override
@ -231,40 +200,33 @@ public class LogFragment extends Fragment {
}
@Override
protected String doInBackground(File... log) {
protected String doInBackground(Void... voids) {
// Ensure initialize is done
try {
WelcomeActivity.initialize.get();
Utils.initialize.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
Thread.currentThread().setPriority(Thread.NORM_PRIORITY + 2);
if (readLog) {
List<String> logList = Utils.readFile(MAGISK_LOG);
StringBuilder llog = new StringBuilder(15 * 10 * 1024);
try {
File logfile = log[0];
BufferedReader br;
br = new BufferedReader(new FileReader(logfile));
long skipped = skipLargeFile(br, logfile.length());
if (skipped > 0) {
llog.append("-----------------\n");
llog.append("Log too long");
llog.append("\n-----------------\n\n");
StringBuilder llog = new StringBuilder(15 * 10 * 1024);
for (String s : logList) {
llog.append(s).append("\n");
}
char[] temp = new char[1024];
int read;
while ((read = br.read(temp)) > 0) {
llog.append(temp, 0, read);
return llog.toString();
} else {
if (Utils.removeFile(MAGISK_LOG)) {
Snackbar.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
} else {
Snackbar.make(txtLog, R.string.logs_clear_failed, Snackbar.LENGTH_SHORT).show();
}
br.close();
} catch (IOException e) {
llog.append("Cannot read log:\n");
llog.append(e.getMessage());
return "";
}
return llog.toString();
}
@Override

View File

@ -4,6 +4,7 @@ import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@ -12,9 +13,12 @@ import android.widget.ImageView;
import android.widget.Switch;
import android.widget.TextView;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
import java.io.IOException;
import java.util.concurrent.ExecutionException;
import butterknife.BindColor;
import butterknife.BindView;
@ -57,12 +61,18 @@ public class MagiskFragment extends Fragment {
View view = inflater.inflate(R.layout.magisk_fragment, container, false);
ButterKnife.bind(this, view);
try {
Utils.initialize.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
updateStatus();
rootToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
Utils.su(b ? "setprop magisk.root 1" : "setprop magisk.root 0");
Shell.su(b ? "setprop magisk.root 1" : "setprop magisk.root 0");
updateStatus();
}
});
@ -70,7 +80,7 @@ public class MagiskFragment extends Fragment {
selinuxToggle.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton compoundButton, boolean b) {
Utils.su(b ? "setenforce 1" : "setenforce 0");
Shell.su(b ? "setenforce 1" : "setenforce 0");
updateStatus();
}
});
@ -87,8 +97,8 @@ public class MagiskFragment extends Fragment {
}
private void updateStatus() {
String selinux = Utils.sh("getenforce");
String version = Utils.sh("getprop magisk.version");
String selinux = Shell.sh("getenforce").get(0);
String version = Shell.sh("getprop magisk.version").get(0);
if (TextUtils.isEmpty(version)) {
magiskStatusContainer.setBackgroundColor(grey500);
@ -134,9 +144,9 @@ public class MagiskFragment extends Fragment {
safetyNetStatusIcon.setImageResource(statusError);
if (!Utils.rootAccess) {
if (!Shell.rootAccess()) {
rootStatusContainer.setBackgroundColor(red500);
rootStatusIcon.setImageResource(statusError);
rootStatusIcon.setImageResource(statusUnknown);
rootStatus.setTextColor(red500);
rootStatus.setText(R.string.root_system);
@ -146,7 +156,7 @@ public class MagiskFragment extends Fragment {
safetyNetStatus.setText(R.string.root_system_info);
} else {
rootStatusContainer.setBackgroundColor(green500);
rootStatusIcon.setImageResource(statusOK);
rootStatusIcon.setImageResource(statusError);
rootStatus.setTextColor(green500);
rootStatus.setText(R.string.root_mounted);
@ -154,14 +164,14 @@ public class MagiskFragment extends Fragment {
}
} else {
rootStatusContainer.setBackgroundColor(green500);
rootStatusIcon.setImageResource(red500);
rootStatusIcon.setImageResource(statusOK);
rootStatus.setTextColor(green500);
rootToggle.setChecked(false);
safetyNetStatusIcon.setImageResource(statusOK);
if (!Utils.rootAccess) {
if (!Shell.rootAccess()) {
rootStatusContainer.setBackgroundColor(red500);
rootStatusIcon.setImageResource(statusError);
rootStatus.setTextColor(red500);

View File

@ -14,6 +14,7 @@ import android.view.ViewGroup;
import android.widget.ProgressBar;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
import java.io.FileFilter;
@ -29,8 +30,8 @@ public class ModulesFragment extends Fragment {
private static final String MAGISK_PATH = "/magisk";
private static final String MAGISK_CACHE_PATH = "/cache/magisk";
private static List<Module> listModules = new ArrayList<>();
private static List<Module> listModulesCache = new ArrayList<>();
// protected static List<Module> listModules = new ArrayList<>();
// protected static List<Module> listModulesCache = new ArrayList<>();
@BindView(R.id.progressBar) ProgressBar progressBar;
@BindView(R.id.pager) ViewPager viewPager;
@ -40,8 +41,8 @@ public class ModulesFragment extends Fragment {
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
listModules.clear();
listModulesCache.clear();
// listModules.clear();
// listModulesCache.clear();
}
@Nullable
@ -62,7 +63,7 @@ public class ModulesFragment extends Fragment {
@Override
protected List<Module> listModules() {
return listModules;
return Utils.listModules;
}
}
@ -71,7 +72,7 @@ public class ModulesFragment extends Fragment {
@Override
protected List<Module> listModules() {
return listModulesCache;
return Utils.listModulesCache;
}
}
@ -82,51 +83,11 @@ public class ModulesFragment extends Fragment {
protected Void doInBackground(Void... voids) {
// Ensure initialize is done
try {
WelcomeActivity.initialize.get();
Utils.initialize.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
File[] magisk = new File(MAGISK_PATH).listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory();
}
});
File[] magiskCache = new File(MAGISK_CACHE_PATH).listFiles(new FileFilter() {
@Override
public boolean accept(File file) {
return file.isDirectory();
}
});
if (magisk != null) {
for (File mod : magisk) {
Module m = new Module(mod);
if (m.isValid()) {
try {
m.parse();
listModules.add(m);
} catch (Exception ignored) {
}
}
}
}
if (magiskCache != null) {
for (File mod : magiskCache) {
Module m = new Module(mod);
if (m.isValid()) {
try {
m.parse();
listModulesCache.add(m);
} catch (Exception ignored) {
}
}
}
}
return null;
}

View File

@ -16,18 +16,22 @@ import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import java.util.List;
import butterknife.BindView;
import butterknife.ButterKnife;
public class WelcomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
private static final String SELECTED_ITEM_ID = "SELECTED_ITEM_ID";
public static Init initialize;
public static View view;
private final Handler mDrawerHandler = new Handler();
@BindView(R.id.toolbar) Toolbar toolbar;
@ -46,9 +50,10 @@ public class WelcomeActivity extends AppCompatActivity implements NavigationView
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR);
}
initialize = new Init();
initialize.execute();
Utils.initialize = new Utils.Init();
Utils.initialize.execute();
setSupportActionBar(toolbar);
@ -152,12 +157,6 @@ public class WelcomeActivity extends AppCompatActivity implements NavigationView
@Override
protected Void doInBackground(Void... voids) {
// Check root access
Utils.checkRoot();
// Permission for java code to read /cache files
if (Utils.rootAccess) {
Utils.su("chmod 755 /cache", "chmod 644 /cache/magisk.log");
}
return null;
}
@ -165,7 +164,7 @@ public class WelcomeActivity extends AppCompatActivity implements NavigationView
protected void onPostExecute(Void v) {
super.onPostExecute(v);
if (!Utils.rootAccess) {
if (!Shell.rootAccess()) {
Snackbar.make(view, R.string.no_root_access, Snackbar.LENGTH_LONG).show();
}
}

View File

@ -1,5 +1,7 @@
package com.topjohnwu.magisk.module;
import android.util.Log;
import com.topjohnwu.magisk.utils.Utils;
import java.io.BufferedReader;
@ -8,71 +10,21 @@ import java.io.FileReader;
public class Module {
private final boolean isValid;
private File mRemoveFile;
private File mDisableFile;
private File mPropFile;
private String mRemoveFile;
private String mDisableFile;
private String mName;
private String mVersion;
private String mDescription;
public Module(File file) {
this.isValid = new File(file + "/module.prop").exists();
private boolean mEnable;
private boolean mRemove;
if (!isValid) return;
mPropFile = new File(file + "/module.prop");
mRemoveFile = new File(file + "/remove");
mDisableFile = new File(file + "/disable");
}
public boolean isValid() {
return isValid && mPropFile != null;
}
public String getName() {
return mName;
}
public String getVersion() {
return mVersion;
}
public String getDescription() {
return mDescription;
}
public void createDisableFile() {
Utils.su("touch " + mDisableFile.getPath());
}
public void removeDisableFile() {
Utils.su("rm -f " + mDisableFile.getPath());
}
public boolean isEnabled() {
return ! mDisableFile.exists();
}
public void createRemoveFile() {
Utils.su("touch " + mRemoveFile.getPath());
}
public void deleteRemoveFile() {
Utils.su("rm -f " + mRemoveFile.getPath());
}
public boolean willBeRemoved() {
return mRemoveFile.exists();
}
public void parse() throws Exception {
BufferedReader reader = new BufferedReader(new FileReader(mPropFile));
String line;
while ((line = reader.readLine()) != null) {
public Module(String path) {
mRemoveFile = path + "/remove";
mDisableFile = path + "/disable";
for (String line : Utils.readFile(path + "/module.prop")) {
String[] parts = line.split("=", 2);
if (parts.length != 2) {
continue;
@ -96,6 +48,46 @@ public class Module {
break;
}
}
reader.close();
mEnable = !Utils.fileExist(mDisableFile);
mRemove = Utils.fileExist(mRemoveFile);
}
public String getName() {
return mName;
}
public String getVersion() {
return mVersion;
}
public String getDescription() {
return mDescription;
}
public void createDisableFile() {
mEnable = !Utils.createFile(mDisableFile);
}
public void removeDisableFile() {
mEnable = Utils.removeFile(mDisableFile);
}
public boolean isEnabled() {
return mEnable;
}
public void createRemoveFile() {
mRemove = Utils.createFile(mRemoveFile);
}
public void deleteRemoveFile() {
mRemove = !Utils.removeFile(mRemoveFile);
}
public boolean willBeRemoved() {
return mRemove;
}
}

View File

@ -11,6 +11,7 @@ import android.widget.TextView;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import java.util.List;
@ -101,7 +102,7 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
public ViewHolder(View itemView) {
super(itemView);
ButterKnife.bind(this, itemView);
if (!Utils.rootAccess) {
if (!Shell.rootAccess()) {
checkBox.setEnabled(false);
delete.setEnabled(false);
}

View File

@ -0,0 +1,172 @@
package com.topjohnwu.magisk.utils;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Modified by topjohnwu, based on Chainfire's libsuperuser
*/
public class Shell {
public static String suPath;
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted; 2 = improperly rooted;
public static int rootStatus = 0;
private static Process rootShell;
private static DataOutputStream rootSTDIN;
private static StreamGobbler rootSTDOUT;
private static List<String> rootOutList = new ArrayList<>();
static {
List<String> ret = sh("getprop magisk.supath");
if(!ret.isEmpty()) {
suPath = ret.get(0) + "/su";
} else {
suPath = "su";
rootStatus = 2;
}
}
public static boolean rootAccess() {
return rootStatus > 0;
}
public static void startRoot() {
rootStatus = rootStatus == 0 ? 1 : 2;
try {
rootShell = Runtime.getRuntime().exec(suPath);
rootSTDIN = new DataOutputStream(rootShell.getOutputStream());
rootSTDOUT = new StreamGobbler(rootShell.getInputStream(), rootOutList);
rootSTDOUT.start();
} catch (IOException e) {
// runtime error! No binary found!
rootStatus = 0;
return;
}
List<String> ret = su("echo -BOC-", "id");
if (ret == null) {
// Something wrong with root install, not enabled?
rootStatus = -1;
return;
}
for (String line : ret) {
if (line.contains("uid=")) {
// id command is working, let's see if we are actually root
rootStatus = line.contains("uid=0") ? rootStatus : -1;
return;
} else if (!line.contains("-BOC-")) {
rootStatus = -1;
return;
}
}
}
@Override
protected void finalize() throws Throwable {
super.finalize();
if (rootAccess()) {
rootSTDIN.write("exit\n".getBytes("UTF-8"));
rootSTDIN.flush();
rootSTDIN.flush();
rootShell.waitFor();
rootSTDIN.close();
rootSTDOUT.join();
rootShell.destroy();
}
}
public static List<String> sh(String... commands) {
List<String> res = Collections.synchronizedList(new ArrayList<String>());
try {
Process process = Runtime.getRuntime().exec("sh");
DataOutputStream STDIN = new DataOutputStream(process.getOutputStream());
StreamGobbler STDOUT = new StreamGobbler(process.getInputStream(), res);
STDOUT.start();
try {
for (String write : commands) {
STDIN.write((write + "\n").getBytes("UTF-8"));
STDIN.flush();
}
STDIN.write("exit\n".getBytes("UTF-8"));
STDIN.flush();
} catch (IOException e) {
if (!e.getMessage().contains("EPIPE")) {
throw e;
}
}
process.waitFor();
try {
STDIN.close();
} catch (IOException e) {
// might be closed already
}
STDOUT.join();
process.destroy();
} catch (IOException | InterruptedException e) {
// shell probably not found
res = null;
}
return res;
}
public static List<String> su(String... commands) {
if(!Shell.rootAccess()) return null;
rootOutList.clear();
try {
try {
for (String write : commands) {
rootSTDIN.write((write + "\n").getBytes("UTF-8"));
rootSTDIN.flush();
}
rootSTDIN.write(("echo \'-done-\'\n").getBytes("UTF-8"));
rootSTDIN.flush();
} catch (IOException e) {
if (!e.getMessage().contains("EPIPE")) {
throw e;
}
}
while (true) {
try {
// Process terminated, it means the interactive shell cannot be initialized
rootShell.exitValue();
return null;
} catch (IllegalThreadStateException e) {
// Process still running, gobble output until done
if (rootOutList != null && !rootOutList.isEmpty()) {
if (rootOutList.get(rootOutList.size() - 1).equals("-done-")) {
rootOutList.remove(rootOutList.size() - 1);
break;
}
}
rootSTDOUT.join(100);
}
}
} catch (IOException | InterruptedException e) {
// shell probably not found
return null;
}
return new ArrayList<>(rootOutList);
}
}

View File

@ -0,0 +1,52 @@
package com.topjohnwu.magisk.utils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
/**
* Modified by topjohnwu, based on Chainfire's libsuperuser
*/
public class StreamGobbler extends Thread {
private BufferedReader reader = null;
private List<String> writer = null;
/**
* <p>StreamGobbler constructor</p>
*
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
* possible to prevent a deadlock from occurring, or Process.waitFor() never
* returning (as the buffer is full, pausing the native process)</p>
*
* @param inputStream InputStream to read from
* @param outputList {@literal List<String>} to write to, or null
*/
public StreamGobbler(InputStream inputStream, List<String> outputList) {
reader = new BufferedReader(new InputStreamReader(inputStream));
writer = outputList;
}
@Override
public void run() {
// keep reading the InputStream until it ends (or an error occurs)
try {
String line;
while ((line = reader.readLine()) != null) {
writer.add(line);
}
} catch (IOException e) {
// reader probably closed, expected exit condition
}
// make sure our stream is closed and resources will be freed
try {
reader.close();
} catch (IOException e) {
// read already closed
}
}
}

View File

@ -1,56 +1,82 @@
package com.topjohnwu.magisk.utils;
import android.os.AsyncTask;
import com.topjohnwu.magisk.module.Module;
import java.util.ArrayList;
import java.util.List;
import eu.chainfire.libsuperuser.Shell;
public class Utils {
public static final String suPath = sh("getprop magisk.supath");
public static boolean rootAccess = false;
public static Init initialize;
public static String sh(String... commands) {
List<String> result = Shell.SH.run(commands);
public static List<Module> listModules = new ArrayList<>();
public static List<Module> listModulesCache = new ArrayList<>();
public static List<String> listLog;
StringBuilder builder = new StringBuilder();
for (String s : result) {
builder.append(s);
}
public static class Init extends AsyncTask<Void, Void, Void> {
return builder.toString();
}
public static String su(String... commands) {
List<String> result = Shell.run(Utils.suPath + "/su", commands, null, false);
StringBuilder builder = new StringBuilder();
for (String s : result) {
builder.append(s);
}
return builder.toString();
}
public static void checkRoot() {
String [] availableTestCommands = new String[] {"echo -BOC-", "id"};
List<String> ret = Shell.run(Utils.suPath + "/su", availableTestCommands, null, false);
if (ret == null)
return;
// Taken from libsuperuser
// this is only one of many ways this can be done
for (String line : ret) {
if (line.contains("uid=")) {
// id command is working, let's see if we are actually root
rootAccess = line.contains("uid=0");
} else if (line.contains("-BOC-")) {
// if we end up here, at least the su command starts some kind
// of shell, let's hope it has root privileges - no way to know without
// additional native binaries
rootAccess = true;
@Override
protected Void doInBackground(Void... voids) {
Shell.startRoot();
listModules.clear();
listModulesCache.clear();
List<String> magisk = getModList("/magisk");
List<String> magiskCache = getModList("/cache/magisk");
if (!magisk.isEmpty()) {
for (String mod : magisk) {
listModules.add(new Module(mod));
}
}
if (!magiskCache.isEmpty()) {
for (String mod : magiskCache) {
listModulesCache.add(new Module(mod));
}
}
listLog = readFile("/cache/magisk.log");
return null;
}
}
public static boolean fileExist(String path) {
List<String> ret;
ret = Shell.sh("if [ -f " + path + " ]; then echo true; else echo false; fi");
if (!Boolean.parseBoolean(ret.get(0)) && Shell.rootAccess()) ret = Shell.su("if [ -f " + path + " ]; then echo true; else echo false; fi");
return Boolean.parseBoolean(ret.get(0));
}
public static boolean createFile(String path) {
if (!Shell.rootAccess()) {
return false;
} else {
return Boolean.parseBoolean(Shell.su("touch " + path + " 2>/dev/null; if [ -f " + path + " ]; then echo true; else echo false; fi").get(0));
}
}
public static boolean removeFile(String path) {
if (!Shell.rootAccess()) {
return false;
} else {
return Boolean.parseBoolean(Shell.su("rm -f " + path + " 2>/dev/null; if [ -f " + path + " ]; then echo false; else echo true; fi").get(0));
}
}
public static List<String> getModList(String path) {
List<String> ret;
ret = Shell.sh("find " + path + " -type d -maxdepth 1 | while read ITEM ; do if [ -f $ITEM/module.prop ]; then echo $ITEM; fi; done");
if (ret.isEmpty() && Shell.rootAccess()) ret = Shell.su("find " + path + " -type d -maxdepth 1 | while read ITEM ; do if [ -f $ITEM/module.prop ]; then echo $ITEM; fi; done");
return ret;
}
public static List<String> readFile(String path) {
List<String> ret;
ret = Shell.sh("cat " + path);
if (ret.isEmpty() && Shell.rootAccess()) ret = Shell.su("cat " + path, "echo \' \'");
return ret;
}
}