Rewrite root shell

This commit is contained in:
topjohnwu 2017-07-16 01:20:39 +08:00
parent cc14a1c361
commit 87ea2a2bef
17 changed files with 159 additions and 255 deletions

View File

@ -197,7 +197,7 @@ public class MagiskFragment extends Fragment
@Override
public void onFinish() {
progress.setMessage(getString(R.string.reboot_countdown, 0));
Shell.su(true,
magiskManager.rootShell.su(
"mv -f " + uninstaller + " /cache/" + MagiskManager.UNINSTALLER,
"mv -f " + utils + " /data/magisk/" + MagiskManager.UTIL_FUNCTIONS,
"reboot"

View File

@ -2,6 +2,7 @@ package com.topjohnwu.magisk;
import android.Manifest;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
@ -24,10 +25,9 @@ import android.widget.ScrollView;
import android.widget.TextView;
import android.widget.Toast;
import com.topjohnwu.magisk.asyncs.RootTask;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.components.SnackbarMaker;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import java.io.File;
@ -67,7 +67,7 @@ public class MagiskLogFragment extends Fragment {
txtLog.setTextIsSelectable(true);
new LogManager().read();
new LogManager(getActivity()).read();
return view;
}
@ -81,7 +81,7 @@ public class MagiskLogFragment extends Fragment {
@Override
public void onResume() {
super.onResume();
new LogManager().read();
new LogManager(getActivity()).read();
}
@Override
@ -100,13 +100,13 @@ public class MagiskLogFragment extends Fragment {
mClickedMenuItem = item;
switch (item.getItemId()) {
case R.id.menu_refresh:
new LogManager().read();
new LogManager(getActivity()).read();
return true;
case R.id.menu_save:
new LogManager().save();
new LogManager(getActivity()).save();
return true;
case R.id.menu_clear:
new LogManager().clear();
new LogManager(getActivity()).clear();
return true;
default:
return true;
@ -127,18 +127,22 @@ public class MagiskLogFragment extends Fragment {
}
}
private class LogManager extends RootTask<Object, Void, Object> {
private class LogManager extends ParallelTask<Object, Void, Object> {
int mode;
File targetFile;
LogManager(Activity activity) {
super(activity);
}
@SuppressLint("DefaultLocale")
@Override
protected Object doInRoot(Object... params) {
protected Object doInBackground(Object... params) {
mode = (int) params[0];
switch (mode) {
case 0:
List<String> logList = Utils.readFile(MAGISK_LOG);
List<String> logList = Utils.readFile(magiskManager.rootShell, MAGISK_LOG);
if (Utils.isValidShellResponse(logList)) {
StringBuilder llog = new StringBuilder(15 * 10 * 1024);
@ -150,7 +154,7 @@ public class MagiskLogFragment extends Fragment {
return "";
case 1:
Shell.su("echo > " + MAGISK_LOG);
magiskManager.rootShell.su("echo > " + MAGISK_LOG);
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
return "";
@ -180,7 +184,7 @@ public class MagiskLogFragment extends Fragment {
return false;
}
List<String> in = Utils.readFile(MAGISK_LOG);
List<String> in = Utils.readFile(magiskManager.rootShell, MAGISK_LOG);
if (Utils.isValidShellResponse(in)) {
try (FileWriter out = new FileWriter(targetFile)) {

View File

@ -88,13 +88,16 @@ public class MagiskManager extends Application {
// Global resources
public SharedPreferences prefs;
public SuDatabaseHelper suDB;
public Shell rootShell;
private static Handler mHandler = new Handler();
@Override
public void onCreate() {
super.onCreate();
new File(getApplicationInfo().dataDir).mkdirs(); /* Create the app data directory */
prefs = PreferenceManager.getDefaultSharedPreferences(this);
rootShell = Shell.getRootShell();
}
public void toast(String msg, int duration) {
@ -117,14 +120,12 @@ public class MagiskManager extends Application {
magiskHide = prefs.getBoolean("magiskhide", true);
updateNotification = prefs.getBoolean("notification", true);
initSU();
// Always start a new root shell manually, just for safety
Shell.init();
updateMagiskInfo();
// Initialize busybox
File busybox = new File(getApplicationInfo().dataDir + "/busybox/busybox");
if (!busybox.exists() || !TextUtils.equals(prefs.getString("busybox_version", ""), BUSYBOX_VERSION)) {
busybox.getParentFile().mkdirs();
Shell.su(
rootShell.su(
"cp -f " + new File(getApplicationInfo().nativeLibraryDir, "libbusybox.so") + " " + busybox,
"chmod -R 755 " + busybox.getParent(),
busybox + " --install -s " + busybox.getParent()
@ -136,7 +137,7 @@ public class MagiskManager extends Application {
.putBoolean("magiskhide", magiskHide)
.putBoolean("notification", updateNotification)
.putBoolean("hosts", new File("/magisk/.core/hosts").exists())
.putBoolean("disable", Utils.itemExist(MAGISK_DISABLE_FILE))
.putBoolean("disable", Utils.itemExist(rootShell, MAGISK_DISABLE_FILE))
.putBoolean("su_reauth", suReauth)
.putString("su_request_timeout", String.valueOf(suRequestTimeout))
.putString("su_auto_response", String.valueOf(suResponseType))
@ -147,7 +148,7 @@ public class MagiskManager extends Application {
.putString("busybox_version", BUSYBOX_VERSION)
.apply();
// Add busybox to PATH
Shell.su("PATH=$PATH:" + busybox.getParent());
rootShell.su("PATH=$PATH:" + busybox.getParent());
// Create notification channel on Android O
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -167,9 +168,6 @@ public class MagiskManager extends Application {
}
public void initSU() {
// Create the app data directory, so su binary can work properly
new File(getApplicationInfo().dataDir).mkdirs();
initSUConfig();
List<String> ret = Shell.sh("su -v");

View File

@ -20,6 +20,7 @@ import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.module.Module;
import com.topjohnwu.magisk.utils.CallbackEvent;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import java.util.ArrayList;
import java.util.List;

View File

@ -149,9 +149,9 @@ public class SettingsActivity extends Activity {
case "disable":
enabled = prefs.getBoolean("disable", false);
if (enabled) {
Utils.createFile(MagiskManager.MAGISK_DISABLE_FILE);
Utils.createFile(magiskManager.rootShell, MagiskManager.MAGISK_DISABLE_FILE);
} else {
Utils.removeItem(MagiskManager.MAGISK_DISABLE_FILE);
Utils.removeItem(magiskManager.rootShell, MagiskManager.MAGISK_DISABLE_FILE);
}
Toast.makeText(getActivity(), R.string.settings_reboot_toast, Toast.LENGTH_LONG).show();
break;
@ -175,11 +175,11 @@ public class SettingsActivity extends Activity {
case "hosts":
enabled = prefs.getBoolean("hosts", false);
if (enabled) {
Shell.su_async(null,
magiskManager.rootShell.su(
"cp -af /system/etc/hosts /magisk/.core/hosts",
"mount -o bind /magisk/.core/hosts /system/etc/hosts");
} else {
Shell.su_async(null,
magiskManager.rootShell.su(
"umount -l /system/etc/hosts",
"rm -f /magisk/.core/hosts");
}

View File

@ -38,6 +38,7 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
@Override
public void onBindViewHolder(final ViewHolder holder, int position) {
Context context = holder.itemView.getContext();
Shell rootShell = Shell.getRootShell(context);
final Module module = mList.get(position);
String version = module.getVersion();
@ -55,10 +56,10 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
int snack;
if (isChecked) {
module.removeDisableFile();
module.removeDisableFile(rootShell);
snack = R.string.disable_file_removed;
} else {
module.createDisableFile();
module.createDisableFile(rootShell);
snack = R.string.disable_file_created;
}
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();
@ -68,10 +69,10 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
boolean removed = module.willBeRemoved();
int snack;
if (removed) {
module.deleteRemoveFile();
module.deleteRemoveFile(rootShell);
snack = R.string.remove_file_deleted;
} else {
module.createRemoveFile();
module.createRemoveFile(rootShell);
snack = R.string.remove_file_created;
}
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show();

View File

@ -9,7 +9,6 @@ import com.topjohnwu.magisk.MagiskManager;
import com.topjohnwu.magisk.R;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ZipUtils;
@ -21,7 +20,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
public class FlashZip extends RootTask<Void, String, Integer> {
public class FlashZip extends ParallelTask<Void, String, Integer> {
private Uri mUri;
private File mCachedFile, mScriptFile, mCheckFile;
@ -71,12 +70,12 @@ public class FlashZip extends RootTask<Void, String, Integer> {
private boolean unzipAndCheck() throws Exception {
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android");
List<String> ret;
ret = Utils.readFile(mCheckFile.getPath());
ret = Utils.readFile(magiskManager.rootShell, mCheckFile.getPath());
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
}
private int cleanup(int ret) {
Shell.su(
magiskManager.rootShell.su(
"rm -rf " + mCachedFile.getParent() + "/*",
"rm -rf " + MagiskManager.TMP_FOLDER_PATH
);
@ -96,14 +95,14 @@ public class FlashZip extends RootTask<Void, String, Integer> {
}
@Override
protected Integer doInRoot(Void... voids) {
protected Integer doInBackground(Void... voids) {
Logger.dev("FlashZip Running... " + mFilename);
List<String> ret;
try {
copyToCache();
if (!unzipAndCheck()) return cleanup(0);
publishProgress(magiskManager.getString(R.string.zip_install_progress_msg, mFilename));
ret = Shell.su(
ret = magiskManager.rootShell.su(
"BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile,
"if [ $? -eq 0 ]; then echo true; else echo false; fi"
);
@ -147,7 +146,7 @@ public class FlashZip extends RootTask<Void, String, Integer> {
new AlertDialogBuilder(activity)
.setTitle(R.string.reboot_title)
.setMessage(R.string.reboot_msg)
.setPositiveButton(R.string.reboot, (dialogInterface, i) -> Shell.su(true, "reboot"))
.setPositiveButton(R.string.reboot, (dialogInterface, i) -> magiskManager.rootShell.su("reboot"))
.setNegativeButton(R.string.no_thanks, null)
.show();
}

View File

@ -2,22 +2,21 @@ package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
public class GetBootBlocks extends RootTask<Void, Void, Void> {
public class GetBootBlocks extends ParallelTask<Void, Void, Void> {
public GetBootBlocks(Activity context) {
super(context);
}
@Override
protected Void doInRoot(Void... params) {
magiskManager.blockList = Shell.su(
protected Void doInBackground(Void... params) {
magiskManager.blockList = magiskManager.rootShell.su(
"find /dev/block -type b -maxdepth 1 | grep -v -E \"loop|ram|dm-0\""
);
if (magiskManager.bootBlock == null) {
magiskManager.bootBlock = Utils.detectBootImage();
magiskManager.bootBlock = Utils.detectBootImage(magiskManager.rootShell);
}
return null;
}

View File

@ -9,23 +9,22 @@ import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ValueSortedMap;
public class LoadModules extends RootTask<Void, Void, Void> {
public class LoadModules extends ParallelTask<Void, Void, Void> {
public LoadModules(Activity context) {
super(context);
}
@Override
protected Void doInRoot(Void... voids) {
protected Void doInBackground(Void... voids) {
Logger.dev("LoadModules: Loading modules");
magiskManager.moduleMap = new ValueSortedMap<>();
for (String path : Utils.getModList(MagiskManager.MAGISK_PATH)) {
for (String path : Utils.getModList(magiskManager.rootShell, MagiskManager.MAGISK_PATH)) {
Logger.dev("LoadModules: Adding modules from " + path);
Module module;
try {
module = new Module(path);
Module module = new Module(magiskManager.rootShell, path);
magiskManager.moduleMap.put(module.getId(), module);
} catch (BaseModule.CacheModException ignored) {}
}

View File

@ -2,11 +2,9 @@ package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import com.topjohnwu.magisk.utils.Shell;
import java.util.List;
public class MagiskHide extends RootTask<Object, Void, Void> {
public class MagiskHide extends ParallelTask<Object, Void, Void> {
private boolean isList = false;
@ -17,9 +15,9 @@ public class MagiskHide extends RootTask<Object, Void, Void> {
}
@Override
protected Void doInRoot(Object... params) {
protected Void doInBackground(Object... params) {
String command = (String) params[0];
List<String> ret = Shell.su("magiskhide --" + command);
List<String> ret = magiskManager.rootShell.su("magiskhide --" + command);
if (isList) {
magiskManager.magiskHideList = ret;
}

View File

@ -33,13 +33,11 @@ public class ProcessMagiskZip extends ParallelTask<Void, Void, Boolean> {
@Override
protected Boolean doInBackground(Void... params) {
if (Shell.rootAccess()) {
synchronized (Shell.lock) {
Shell.su("rm -f /dev/.magisk",
(mBoot != null) ? "echo \"BOOTIMAGE=" + mBoot + "\" >> /dev/.magisk" : "",
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk"
);
}
magiskManager.rootShell.su("rm -f /dev/.magisk",
(mBoot != null) ? "echo \"BOOTIMAGE=" + mBoot + "\" >> /dev/.magisk" : "",
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk"
);
return true;
}
return false;

View File

@ -1,33 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import com.topjohnwu.magisk.utils.Shell;
public abstract class RootTask <Params, Progress, Result> extends ParallelTask<Params, Progress, Result> {
public RootTask() {}
public RootTask(Activity context) {
super(context);
}
@SafeVarargs
@Override
final protected Result doInBackground(Params... params) {
synchronized (Shell.lock) {
return doInRoot(params);
}
}
@SuppressWarnings("unchecked")
abstract protected Result doInRoot(Params... params);
@SuppressWarnings("unchecked")
@Override
public void exec(Params... params) {
if (Shell.rootAccess()) {
super.exec(params);
}
}
}

View File

@ -1,6 +1,7 @@
package com.topjohnwu.magisk.module;
import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils;
public class Module extends BaseModule {
@ -8,9 +9,9 @@ public class Module extends BaseModule {
private String mRemoveFile, mDisableFile, mUpdateFile;
private boolean mEnable, mRemove, mUpdated;
public Module(String path) throws CacheModException {
public Module(Shell shell, String path) throws CacheModException {
parseProps(Utils.readFile(path + "/module.prop"));
parseProps(Utils.readFile(shell, path + "/module.prop"));
mRemoveFile = path + "/remove";
mDisableFile = path + "/disable";
@ -27,33 +28,33 @@ public class Module extends BaseModule {
Logger.dev("Creating Module, id: " + getId());
mEnable = !Utils.itemExist(mDisableFile);
mRemove = Utils.itemExist(mRemoveFile);
mUpdated = Utils.itemExist(mUpdateFile);
mEnable = !Utils.itemExist(shell, mDisableFile);
mRemove = Utils.itemExist(shell, mRemoveFile);
mUpdated = Utils.itemExist(shell, mUpdateFile);
}
public void createDisableFile() {
public void createDisableFile(Shell shell) {
mEnable = false;
Utils.createFile(mDisableFile);
Utils.createFile(shell, mDisableFile);
}
public void removeDisableFile() {
public void removeDisableFile(Shell shell) {
mEnable = true;
Utils.removeItem(mDisableFile);
Utils.removeItem(shell, mDisableFile);
}
public boolean isEnabled() {
return mEnable;
}
public void createRemoveFile() {
public void createRemoveFile(Shell shell) {
mRemove = true;
Utils.createFile(mRemoveFile);
Utils.createFile(shell, mRemoveFile);
}
public void deleteRemoveFile() {
public void deleteRemoveFile(Shell shell) {
mRemove = false;
Utils.removeItem(mRemoveFile);
Utils.removeItem(shell, mRemoveFile);
}
public boolean willBeRemoved() {

View File

@ -11,23 +11,39 @@ public class Logger {
public static final String MAIN_TAG = "Magisk";
public static final String DEBUG_TAG = "MagiskManager";
public static void debug(String line) {
Log.d(DEBUG_TAG, "DEBUG: " + line);
}
public static void debug(String fmt, Object... args) {
Log.d(DEBUG_TAG, "DEBUG: " + String.format(Locale.US, fmt, args));
debug(String.format(Locale.US, fmt, args));
}
public static void error(String line) {
Log.e(MAIN_TAG, "MANAGERERROR: " + line);
}
public static void error(String fmt, Object... args) {
Log.e(MAIN_TAG, "MANAGERERROR: " + String.format(Locale.US, fmt, args));
error(String.format(Locale.US, fmt, args));
}
public static void dev(String line) {
if (MagiskManager.devLogging) {
Log.d(DEBUG_TAG, line);
}
}
public static void dev(String fmt, Object... args) {
if (MagiskManager.devLogging) {
Log.d(DEBUG_TAG, String.format(Locale.US, fmt, args));
dev(String.format(Locale.US, fmt, args));
}
public static void shell(boolean root, String line) {
if (MagiskManager.shellLogging) {
Log.d(DEBUG_TAG, (root ? "MANAGERSU: " : "MANAGERSH: ") + line);
}
}
public static void shell(boolean root, String fmt, Object... args) {
if (MagiskManager.shellLogging) {
Log.d(DEBUG_TAG, (root ? "MANAGERSU: " : "MANAGERSH: ") + String.format(Locale.US, fmt, args));
}
shell(root, String.format(Locale.US, fmt, args));
}
}

View File

@ -1,7 +1,8 @@
package com.topjohnwu.magisk.utils;
import com.topjohnwu.magisk.asyncs.RootTask;
import android.content.Context;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.ArrayList;
@ -16,37 +17,35 @@ public class Shell {
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
public static int rootStatus;
public static final Object lock = new Object();
private static boolean isInit = false;
private static Process rootShell;
private static DataOutputStream rootSTDIN;
private static StreamGobbler rootSTDOUT;
private static List<String> rootOutList = Collections.synchronizedList(new ArrayList<String>());
public static void init() {
isInit = true;
private final Process rootShell;
private final DataOutputStream rootSTDIN;
private final DataInputStream rootSTDOUT;
private Shell() {
Process process;
try {
rootShell = Runtime.getRuntime().exec("su");
rootStatus = 1;
} catch (IOException err) {
process = Runtime.getRuntime().exec("su");
} catch (IOException e) {
// No root
rootStatus = 0;
rootShell = null;
rootSTDIN = null;
rootSTDOUT = null;
return;
}
rootStatus = 1;
rootShell = process;
rootSTDIN = new DataOutputStream(rootShell.getOutputStream());
rootSTDOUT = new StreamGobbler(rootShell.getInputStream(), rootOutList, true);
rootSTDOUT.start();
rootSTDOUT = new DataInputStream(rootShell.getInputStream());
// Setup umask and PATH
su("umask 022");
List<String> ret = su("echo -BOC-", "id");
if (ret == null) {
if (ret.isEmpty()) {
// Something wrong with root, not allowed?
rootStatus = -1;
return;
@ -55,7 +54,7 @@ public class Shell {
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;
rootStatus = line.contains("uid=0") ? 1 : -1;
return;
} else if (!line.contains("-BOC-")) {
rootStatus = -1;
@ -64,8 +63,16 @@ public class Shell {
}
}
public static Shell getRootShell() {
return new Shell();
}
public static Shell getRootShell(Context context) {
return Utils.getMagiskManager(context).rootShell;
}
public static boolean rootAccess() {
return isInit && rootStatus > 0;
return rootStatus > 0;
}
public static List<String> sh(String... commands) {
@ -110,120 +117,34 @@ public class Shell {
return res;
}
// Run with the same shell by default
public static List<String> su(String... commands) {
return su(false, commands);
public List<String> su(String... commands) {
List<String> res = new ArrayList<>();
su(res, commands);
return res;
}
public static List<String> su(boolean newShell, String... commands) {
List<String> res;
Process process;
DataOutputStream STDIN;
StreamGobbler STDOUT;
// Create the default shell if not init
if (!newShell && !isInit) {
init();
}
if (!newShell && !rootAccess()) {
return null;
}
if (newShell) {
res = Collections.synchronizedList(new ArrayList<String>());
try {
process = Runtime.getRuntime().exec("su");
STDIN = new DataOutputStream(process.getOutputStream());
STDOUT = new StreamGobbler(process.getInputStream(), res);
// Run the new shell with busybox and proper umask
STDIN.write(("umask 022\n").getBytes("UTF-8"));
STDIN.flush();
} catch (IOException err) {
return null;
}
STDOUT.start();
} else {
process = rootShell;
STDIN = rootSTDIN;
STDOUT = rootSTDOUT;
res = rootOutList;
res.clear();
}
public void su(List<String> res, String... commands) {
try {
for (String write : commands) {
STDIN.write((write + "\n").getBytes("UTF-8"));
STDIN.flush();
Logger.shell(true, write);
}
if (newShell) {
STDIN.write("exit\n".getBytes("UTF-8"));
STDIN.flush();
process.waitFor();
try {
STDIN.close();
} catch (IOException ignore) {
// might be closed already
}
STDOUT.join();
process.destroy();
} else {
STDIN.write(("echo\n").getBytes("UTF-8"));
STDIN.flush();
STDIN.write(("echo \'-root-done-\'\n").getBytes("UTF-8"));
STDIN.flush();
while (true) {
try {
// Process terminated, it means the interactive shell has some issues
process.exitValue();
rootStatus = -1;
return null;
} catch (IllegalThreadStateException e) {
// Process still running, gobble output until done
int end = res.size() - 1;
if (end > 0) {
if (res.get(end).equals("-root-done-")) {
res.remove(end);
if (res.get(end -1).isEmpty()) {
res.remove(end -1);
}
break;
}
}
try { STDOUT.join(100); } catch (InterruptedException err) {
rootStatus = -1;
return null;
}
}
}
}
} catch (IOException e) {
if (!e.getMessage().contains("EPIPE")) {
Logger.dev("Shell: Root shell error...");
rootStatus = -1;
return null;
}
} catch(InterruptedException e) {
Logger.dev("Shell: Root shell error...");
rootStatus = -1;
return null;
rootShell.exitValue();
return; // The process is dead, return
} catch (IllegalThreadStateException ignored) {
// This should be the expected result
}
return new ArrayList<>(res);
}
public static void su_async(List<String> result, String... commands) {
new RootTask<Void, Void, Void>() {
@Override
protected Void doInRoot(Void... params) {
List<String> ret = Shell.su(commands);
if (result != null) result.addAll(ret);
return null;
synchronized (rootShell) {
StreamGobbler STDOUT = new StreamGobbler(rootSTDOUT, Collections.synchronizedList(res), true);
STDOUT.start();
try {
for (String command : commands) {
rootSTDIN.write((command + "\n").getBytes("UTF-8"));
rootSTDIN.flush();
}
rootSTDIN.write(("echo \'-root-done-\'\n").getBytes("UTF-8"));
rootSTDIN.flush();
STDOUT.join();
} catch (InterruptedException | IOException e) {
e.printStackTrace();
rootShell.destroy();
}
}.exec();
}
}
}

View File

@ -1,5 +1,7 @@
package com.topjohnwu.magisk.utils;
import android.text.TextUtils;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
@ -43,10 +45,10 @@ public class StreamGobbler extends Thread {
try {
String line;
while ((line = reader.readLine()) != null) {
if (TextUtils.equals(line, "-root-done-"))
return;
writer.add(line);
if (!line.equals("-root-done-") && !line.isEmpty()) {
Logger.shell(isRoot, "OUT: " + line);
}
Logger.shell(isRoot, "OUT: " + line);
}
} catch (IOException e) {
// reader probably closed, expected exit condition

View File

@ -43,33 +43,33 @@ public class Utils {
private static final int MAGISK_UPDATE_NOTIFICATION_ID = 1;
private static final int APK_UPDATE_NOTIFICATION_ID = 2;
public static boolean itemExist(String path) {
public static boolean itemExist(Shell shell, String path) {
String command = "if [ -e " + path + " ]; then echo true; else echo false; fi";
List<String> ret = Shell.su(command);
List<String> ret = shell.su(command);
return isValidShellResponse(ret) && Boolean.parseBoolean(ret.get(0));
}
public static void createFile(String path) {
public static void createFile(Shell shell, String path) {
String folder = path.substring(0, path.lastIndexOf('/'));
String command = "mkdir -p " + folder + " 2>/dev/null; touch " + path + " 2>/dev/null; if [ -f \"" + path + "\" ]; then echo true; else echo false; fi";
Shell.su_async(null, command);
shell.su(command);
}
public static void removeItem(String path) {
public static void removeItem(Shell shell, String path) {
String command = "rm -rf " + path + " 2>/dev/null; if [ -e " + path + " ]; then echo false; else echo true; fi";
Shell.su_async(null, command);
shell.su(command);
}
public static List<String> getModList(String path) {
public static List<String> getModList(Shell shell, String path) {
String command = "find " + path + " -type d -maxdepth 1 ! -name \"*.core\" ! -name \"*lost+found\" ! -name \"*magisk\"";
return Shell.su(command);
return shell.su(command);
}
public static List<String> readFile(String path) {
public static List<String> readFile(Shell shell, String path) {
List<String> ret;
String command = "cat " + path;
if (Shell.rootAccess()) {
ret = Shell.su(command);
ret = shell.su(command);
} else {
ret = Shell.sh(command);
}
@ -114,7 +114,7 @@ public class Utils {
.replace("#", "").replace("@", "").replace("*", "");
}
public static String detectBootImage() {
public static String detectBootImage(Shell shell) {
String[] commands = {
"for PARTITION in kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do",
"BOOTIMAGE=`readlink /dev/block/by-name/$PARTITION || " +
@ -124,7 +124,7 @@ public class Utils {
"done",
"echo \"$BOOTIMAGE\""
};
List<String> ret = Shell.su(commands);
List<String> ret = shell.su(commands);
if (isValidShellResponse(ret)) {
return ret.get(0);
}