From 87ea2a2bef0e13041efa3eb6f9a90ca5af239651 Mon Sep 17 00:00:00 2001 From: topjohnwu Date: Sun, 16 Jul 2017 01:20:39 +0800 Subject: [PATCH] Rewrite root shell --- .../com/topjohnwu/magisk/MagiskFragment.java | 2 +- .../topjohnwu/magisk/MagiskLogFragment.java | 28 +-- .../com/topjohnwu/magisk/MagiskManager.java | 14 +- .../com/topjohnwu/magisk/ModulesFragment.java | 1 + .../topjohnwu/magisk/SettingsActivity.java | 8 +- .../magisk/adapters/ModulesAdapter.java | 9 +- .../com/topjohnwu/magisk/asyncs/FlashZip.java | 13 +- .../magisk/asyncs/GetBootBlocks.java | 9 +- .../topjohnwu/magisk/asyncs/LoadModules.java | 9 +- .../topjohnwu/magisk/asyncs/MagiskHide.java | 8 +- .../magisk/asyncs/ProcessMagiskZip.java | 12 +- .../com/topjohnwu/magisk/asyncs/RootTask.java | 33 ---- .../com/topjohnwu/magisk/module/Module.java | 27 +-- .../com/topjohnwu/magisk/utils/Logger.java | 30 ++- .../com/topjohnwu/magisk/utils/Shell.java | 179 +++++------------- .../topjohnwu/magisk/utils/StreamGobbler.java | 8 +- .../com/topjohnwu/magisk/utils/Utils.java | 24 +-- 17 files changed, 159 insertions(+), 255 deletions(-) delete mode 100644 app/src/main/java/com/topjohnwu/magisk/asyncs/RootTask.java diff --git a/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java b/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java index a45d5a10c..feb22de94 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/MagiskFragment.java @@ -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" diff --git a/app/src/main/java/com/topjohnwu/magisk/MagiskLogFragment.java b/app/src/main/java/com/topjohnwu/magisk/MagiskLogFragment.java index 3245c86cf..1976900b2 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MagiskLogFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/MagiskLogFragment.java @@ -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 { + private class LogManager extends ParallelTask { 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 logList = Utils.readFile(MAGISK_LOG); + List 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 in = Utils.readFile(MAGISK_LOG); + List in = Utils.readFile(magiskManager.rootShell, MAGISK_LOG); if (Utils.isValidShellResponse(in)) { try (FileWriter out = new FileWriter(targetFile)) { diff --git a/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java b/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java index 5519e59c1..e1d943b0a 100644 --- a/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java +++ b/app/src/main/java/com/topjohnwu/magisk/MagiskManager.java @@ -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 ret = Shell.sh("su -v"); diff --git a/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java b/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java index d03e5162d..34907db8d 100644 --- a/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java +++ b/app/src/main/java/com/topjohnwu/magisk/ModulesFragment.java @@ -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; diff --git a/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java b/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java index c7ce9aef4..ea4473b9e 100644 --- a/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java +++ b/app/src/main/java/com/topjohnwu/magisk/SettingsActivity.java @@ -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"); } diff --git a/app/src/main/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java b/app/src/main/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java index fb305ba1c..e35d19818 100644 --- a/app/src/main/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java +++ b/app/src/main/java/com/topjohnwu/magisk/adapters/ModulesAdapter.java @@ -38,6 +38,7 @@ public class ModulesAdapter extends RecyclerView.Adapter { 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 { +public class FlashZip extends ParallelTask { private Uri mUri; private File mCachedFile, mScriptFile, mCheckFile; @@ -71,12 +70,12 @@ public class FlashZip extends RootTask { private boolean unzipAndCheck() throws Exception { ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android"); List 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 { } @Override - protected Integer doInRoot(Void... voids) { + protected Integer doInBackground(Void... voids) { Logger.dev("FlashZip Running... " + mFilename); List 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 { 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(); } diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/GetBootBlocks.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/GetBootBlocks.java index f445a0f8e..0a83aac1b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/GetBootBlocks.java +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/GetBootBlocks.java @@ -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 { +public class GetBootBlocks extends ParallelTask { 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; } diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/LoadModules.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/LoadModules.java index 20fbf913d..2ada5ba51 100644 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/LoadModules.java +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/LoadModules.java @@ -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 { +public class LoadModules extends ParallelTask { 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) {} } diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/MagiskHide.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/MagiskHide.java index 11fe3b4c2..fea8d36eb 100644 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/MagiskHide.java +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/MagiskHide.java @@ -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 { +public class MagiskHide extends ParallelTask { private boolean isList = false; @@ -17,9 +15,9 @@ public class MagiskHide extends RootTask { } @Override - protected Void doInRoot(Object... params) { + protected Void doInBackground(Object... params) { String command = (String) params[0]; - List ret = Shell.su("magiskhide --" + command); + List ret = magiskManager.rootShell.su("magiskhide --" + command); if (isList) { magiskManager.magiskHideList = ret; } diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessMagiskZip.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessMagiskZip.java index 7bda3f842..bdecbe9e5 100644 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessMagiskZip.java +++ b/app/src/main/java/com/topjohnwu/magisk/asyncs/ProcessMagiskZip.java @@ -33,13 +33,11 @@ public class ProcessMagiskZip extends ParallelTask { @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; diff --git a/app/src/main/java/com/topjohnwu/magisk/asyncs/RootTask.java b/app/src/main/java/com/topjohnwu/magisk/asyncs/RootTask.java deleted file mode 100644 index 50d942224..000000000 --- a/app/src/main/java/com/topjohnwu/magisk/asyncs/RootTask.java +++ /dev/null @@ -1,33 +0,0 @@ -package com.topjohnwu.magisk.asyncs; - -import android.app.Activity; - -import com.topjohnwu.magisk.utils.Shell; - -public abstract class RootTask extends ParallelTask { - - 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); - } - } -} diff --git a/app/src/main/java/com/topjohnwu/magisk/module/Module.java b/app/src/main/java/com/topjohnwu/magisk/module/Module.java index 07c4fb59c..079c90b6f 100644 --- a/app/src/main/java/com/topjohnwu/magisk/module/Module.java +++ b/app/src/main/java/com/topjohnwu/magisk/module/Module.java @@ -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() { diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Logger.java b/app/src/main/java/com/topjohnwu/magisk/utils/Logger.java index 51ad587aa..fae15417b 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Logger.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Logger.java @@ -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)); } } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Shell.java b/app/src/main/java/com/topjohnwu/magisk/utils/Shell.java index bb0226672..29435b729 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Shell.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Shell.java @@ -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 rootOutList = Collections.synchronizedList(new ArrayList()); - 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 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 sh(String... commands) { @@ -110,120 +117,34 @@ public class Shell { return res; } - // Run with the same shell by default - public static List su(String... commands) { - return su(false, commands); + public List su(String... commands) { + List res = new ArrayList<>(); + su(res, commands); + return res; } - public static List su(boolean newShell, String... commands) { - List 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()); - 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 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 result, String... commands) { - new RootTask() { - @Override - protected Void doInRoot(Void... params) { - List 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(); + } } } diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/StreamGobbler.java b/app/src/main/java/com/topjohnwu/magisk/utils/StreamGobbler.java index 917cf1f5e..479d53850 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/StreamGobbler.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/StreamGobbler.java @@ -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 diff --git a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java index c61bd0ec3..19d985d2c 100644 --- a/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java +++ b/app/src/main/java/com/topjohnwu/magisk/utils/Utils.java @@ -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 ret = Shell.su(command); + List 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 getModList(String path) { + public static List 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 readFile(String path) { + public static List readFile(Shell shell, String path) { List 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 ret = Shell.su(commands); + List ret = shell.su(commands); if (isValidShellResponse(ret)) { return ret.get(0); }