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 @Override
public void onFinish() { public void onFinish() {
progress.setMessage(getString(R.string.reboot_countdown, 0)); progress.setMessage(getString(R.string.reboot_countdown, 0));
Shell.su(true, magiskManager.rootShell.su(
"mv -f " + uninstaller + " /cache/" + MagiskManager.UNINSTALLER, "mv -f " + uninstaller + " /cache/" + MagiskManager.UNINSTALLER,
"mv -f " + utils + " /data/magisk/" + MagiskManager.UTIL_FUNCTIONS, "mv -f " + utils + " /data/magisk/" + MagiskManager.UTIL_FUNCTIONS,
"reboot" "reboot"

View File

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

View File

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

View File

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

View File

@ -38,6 +38,7 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
@Override @Override
public void onBindViewHolder(final ViewHolder holder, int position) { public void onBindViewHolder(final ViewHolder holder, int position) {
Context context = holder.itemView.getContext(); Context context = holder.itemView.getContext();
Shell rootShell = Shell.getRootShell(context);
final Module module = mList.get(position); final Module module = mList.get(position);
String version = module.getVersion(); String version = module.getVersion();
@ -55,10 +56,10 @@ public class ModulesAdapter extends RecyclerView.Adapter<ModulesAdapter.ViewHold
holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> { holder.checkBox.setOnCheckedChangeListener((v, isChecked) -> {
int snack; int snack;
if (isChecked) { if (isChecked) {
module.removeDisableFile(); module.removeDisableFile(rootShell);
snack = R.string.disable_file_removed; snack = R.string.disable_file_removed;
} else { } else {
module.createDisableFile(); module.createDisableFile(rootShell);
snack = R.string.disable_file_created; snack = R.string.disable_file_created;
} }
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show(); 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(); boolean removed = module.willBeRemoved();
int snack; int snack;
if (removed) { if (removed) {
module.deleteRemoveFile(); module.deleteRemoveFile(rootShell);
snack = R.string.remove_file_deleted; snack = R.string.remove_file_deleted;
} else { } else {
module.createRemoveFile(); module.createRemoveFile(rootShell);
snack = R.string.remove_file_created; snack = R.string.remove_file_created;
} }
SnackbarMaker.make(holder.itemView, snack, Snackbar.LENGTH_SHORT).show(); 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.R;
import com.topjohnwu.magisk.components.AlertDialogBuilder; import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.utils.Logger; import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
import com.topjohnwu.magisk.utils.ZipUtils; import com.topjohnwu.magisk.utils.ZipUtils;
@ -21,7 +20,7 @@ import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.util.List; import java.util.List;
public class FlashZip extends RootTask<Void, String, Integer> { public class FlashZip extends ParallelTask<Void, String, Integer> {
private Uri mUri; private Uri mUri;
private File mCachedFile, mScriptFile, mCheckFile; private File mCachedFile, mScriptFile, mCheckFile;
@ -71,12 +70,12 @@ public class FlashZip extends RootTask<Void, String, Integer> {
private boolean unzipAndCheck() throws Exception { private boolean unzipAndCheck() throws Exception {
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android"); ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android");
List<String> ret; List<String> ret;
ret = Utils.readFile(mCheckFile.getPath()); ret = Utils.readFile(magiskManager.rootShell, mCheckFile.getPath());
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK"); return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
} }
private int cleanup(int ret) { private int cleanup(int ret) {
Shell.su( magiskManager.rootShell.su(
"rm -rf " + mCachedFile.getParent() + "/*", "rm -rf " + mCachedFile.getParent() + "/*",
"rm -rf " + MagiskManager.TMP_FOLDER_PATH "rm -rf " + MagiskManager.TMP_FOLDER_PATH
); );
@ -96,14 +95,14 @@ public class FlashZip extends RootTask<Void, String, Integer> {
} }
@Override @Override
protected Integer doInRoot(Void... voids) { protected Integer doInBackground(Void... voids) {
Logger.dev("FlashZip Running... " + mFilename); Logger.dev("FlashZip Running... " + mFilename);
List<String> ret; List<String> ret;
try { try {
copyToCache(); copyToCache();
if (!unzipAndCheck()) return cleanup(0); if (!unzipAndCheck()) return cleanup(0);
publishProgress(magiskManager.getString(R.string.zip_install_progress_msg, mFilename)); publishProgress(magiskManager.getString(R.string.zip_install_progress_msg, mFilename));
ret = Shell.su( ret = magiskManager.rootShell.su(
"BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile, "BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile,
"if [ $? -eq 0 ]; then echo true; else echo false; fi" "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) new AlertDialogBuilder(activity)
.setTitle(R.string.reboot_title) .setTitle(R.string.reboot_title)
.setMessage(R.string.reboot_msg) .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) .setNegativeButton(R.string.no_thanks, null)
.show(); .show();
} }

View File

@ -2,22 +2,21 @@ package com.topjohnwu.magisk.asyncs;
import android.app.Activity; import android.app.Activity;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils; 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) { public GetBootBlocks(Activity context) {
super(context); super(context);
} }
@Override @Override
protected Void doInRoot(Void... params) { protected Void doInBackground(Void... params) {
magiskManager.blockList = Shell.su( magiskManager.blockList = magiskManager.rootShell.su(
"find /dev/block -type b -maxdepth 1 | grep -v -E \"loop|ram|dm-0\"" "find /dev/block -type b -maxdepth 1 | grep -v -E \"loop|ram|dm-0\""
); );
if (magiskManager.bootBlock == null) { if (magiskManager.bootBlock == null) {
magiskManager.bootBlock = Utils.detectBootImage(); magiskManager.bootBlock = Utils.detectBootImage(magiskManager.rootShell);
} }
return null; 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.Utils;
import com.topjohnwu.magisk.utils.ValueSortedMap; 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) { public LoadModules(Activity context) {
super(context); super(context);
} }
@Override @Override
protected Void doInRoot(Void... voids) { protected Void doInBackground(Void... voids) {
Logger.dev("LoadModules: Loading modules"); Logger.dev("LoadModules: Loading modules");
magiskManager.moduleMap = new ValueSortedMap<>(); 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); Logger.dev("LoadModules: Adding modules from " + path);
Module module;
try { try {
module = new Module(path); Module module = new Module(magiskManager.rootShell, path);
magiskManager.moduleMap.put(module.getId(), module); magiskManager.moduleMap.put(module.getId(), module);
} catch (BaseModule.CacheModException ignored) {} } catch (BaseModule.CacheModException ignored) {}
} }

View File

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

View File

@ -33,13 +33,11 @@ public class ProcessMagiskZip extends ParallelTask<Void, Void, Boolean> {
@Override @Override
protected Boolean doInBackground(Void... params) { protected Boolean doInBackground(Void... params) {
if (Shell.rootAccess()) { if (Shell.rootAccess()) {
synchronized (Shell.lock) { magiskManager.rootShell.su("rm -f /dev/.magisk",
Shell.su("rm -f /dev/.magisk",
(mBoot != null) ? "echo \"BOOTIMAGE=" + mBoot + "\" >> /dev/.magisk" : "", (mBoot != null) ? "echo \"BOOTIMAGE=" + mBoot + "\" >> /dev/.magisk" : "",
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk", "echo \"KEEPFORCEENCRYPT=" + String.valueOf(mEnc) + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk" "echo \"KEEPVERITY=" + String.valueOf(mVerity) + "\" >> /dev/.magisk"
); );
}
return true; return true;
} }
return false; 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; package com.topjohnwu.magisk.module;
import com.topjohnwu.magisk.utils.Logger; import com.topjohnwu.magisk.utils.Logger;
import com.topjohnwu.magisk.utils.Shell;
import com.topjohnwu.magisk.utils.Utils; import com.topjohnwu.magisk.utils.Utils;
public class Module extends BaseModule { public class Module extends BaseModule {
@ -8,9 +9,9 @@ public class Module extends BaseModule {
private String mRemoveFile, mDisableFile, mUpdateFile; private String mRemoveFile, mDisableFile, mUpdateFile;
private boolean mEnable, mRemove, mUpdated; 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"; mRemoveFile = path + "/remove";
mDisableFile = path + "/disable"; mDisableFile = path + "/disable";
@ -27,33 +28,33 @@ public class Module extends BaseModule {
Logger.dev("Creating Module, id: " + getId()); Logger.dev("Creating Module, id: " + getId());
mEnable = !Utils.itemExist(mDisableFile); mEnable = !Utils.itemExist(shell, mDisableFile);
mRemove = Utils.itemExist(mRemoveFile); mRemove = Utils.itemExist(shell, mRemoveFile);
mUpdated = Utils.itemExist(mUpdateFile); mUpdated = Utils.itemExist(shell, mUpdateFile);
} }
public void createDisableFile() { public void createDisableFile(Shell shell) {
mEnable = false; mEnable = false;
Utils.createFile(mDisableFile); Utils.createFile(shell, mDisableFile);
} }
public void removeDisableFile() { public void removeDisableFile(Shell shell) {
mEnable = true; mEnable = true;
Utils.removeItem(mDisableFile); Utils.removeItem(shell, mDisableFile);
} }
public boolean isEnabled() { public boolean isEnabled() {
return mEnable; return mEnable;
} }
public void createRemoveFile() { public void createRemoveFile(Shell shell) {
mRemove = true; mRemove = true;
Utils.createFile(mRemoveFile); Utils.createFile(shell, mRemoveFile);
} }
public void deleteRemoveFile() { public void deleteRemoveFile(Shell shell) {
mRemove = false; mRemove = false;
Utils.removeItem(mRemoveFile); Utils.removeItem(shell, mRemoveFile);
} }
public boolean willBeRemoved() { public boolean willBeRemoved() {

View File

@ -11,23 +11,39 @@ public class Logger {
public static final String MAIN_TAG = "Magisk"; public static final String MAIN_TAG = "Magisk";
public static final String DEBUG_TAG = "MagiskManager"; 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) { 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) { 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) { public static void dev(String fmt, Object... args) {
if (MagiskManager.devLogging) { dev(String.format(Locale.US, fmt, args));
Log.d(DEBUG_TAG, 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) { public static void shell(boolean root, String fmt, Object... args) {
if (MagiskManager.shellLogging) { shell(root, String.format(Locale.US, fmt, args));
Log.d(DEBUG_TAG, (root ? "MANAGERSU: " : "MANAGERSH: ") + String.format(Locale.US, fmt, args));
}
} }
} }

View File

@ -1,7 +1,8 @@
package com.topjohnwu.magisk.utils; 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.DataOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -16,37 +17,35 @@ public class Shell {
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted // -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
public static int rootStatus; public static int rootStatus;
public static final Object lock = new Object();
private static boolean isInit = false; 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() { private final Process rootShell;
private final DataOutputStream rootSTDIN;
isInit = true; private final DataInputStream rootSTDOUT;
private Shell() {
Process process;
try { try {
rootShell = Runtime.getRuntime().exec("su"); process = Runtime.getRuntime().exec("su");
rootStatus = 1; } catch (IOException e) {
} catch (IOException err) {
// No root // No root
rootStatus = 0; rootStatus = 0;
rootShell = null;
rootSTDIN = null;
rootSTDOUT = null;
return; return;
} }
rootStatus = 1;
rootShell = process;
rootSTDIN = new DataOutputStream(rootShell.getOutputStream()); rootSTDIN = new DataOutputStream(rootShell.getOutputStream());
rootSTDOUT = new StreamGobbler(rootShell.getInputStream(), rootOutList, true); rootSTDOUT = new DataInputStream(rootShell.getInputStream());
rootSTDOUT.start();
// Setup umask and PATH
su("umask 022"); su("umask 022");
List<String> ret = su("echo -BOC-", "id"); List<String> ret = su("echo -BOC-", "id");
if (ret == null) { if (ret.isEmpty()) {
// Something wrong with root, not allowed? // Something wrong with root, not allowed?
rootStatus = -1; rootStatus = -1;
return; return;
@ -55,7 +54,7 @@ public class Shell {
for (String line : ret) { for (String line : ret) {
if (line.contains("uid=")) { if (line.contains("uid=")) {
// id command is working, let's see if we are actually root // 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; return;
} else if (!line.contains("-BOC-")) { } else if (!line.contains("-BOC-")) {
rootStatus = -1; 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() { public static boolean rootAccess() {
return isInit && rootStatus > 0; return rootStatus > 0;
} }
public static List<String> sh(String... commands) { public static List<String> sh(String... commands) {
@ -110,120 +117,34 @@ public class Shell {
return res; return res;
} }
// Run with the same shell by default public List<String> su(String... commands) {
public static List<String> su(String... commands) { List<String> res = new ArrayList<>();
return su(false, commands); su(res, commands);
return res;
} }
public static List<String> su(boolean newShell, String... commands) { public void su(List<String> res, 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 { try {
process = Runtime.getRuntime().exec("su"); rootShell.exitValue();
STDIN = new DataOutputStream(process.getOutputStream()); return; // The process is dead, return
STDOUT = new StreamGobbler(process.getInputStream(), res); } catch (IllegalThreadStateException ignored) {
// This should be the expected result
// Run the new shell with busybox and proper umask
STDIN.write(("umask 022\n").getBytes("UTF-8"));
STDIN.flush();
} catch (IOException err) {
return null;
} }
synchronized (rootShell) {
StreamGobbler STDOUT = new StreamGobbler(rootSTDOUT, Collections.synchronizedList(res), true);
STDOUT.start(); STDOUT.start();
} else {
process = rootShell;
STDIN = rootSTDIN;
STDOUT = rootSTDOUT;
res = rootOutList;
res.clear();
}
try { try {
for (String write : commands) { for (String command : commands) {
STDIN.write((write + "\n").getBytes("UTF-8")); rootSTDIN.write((command + "\n").getBytes("UTF-8"));
STDIN.flush(); rootSTDIN.flush();
Logger.shell(true, write);
} }
if (newShell) { rootSTDIN.write(("echo \'-root-done-\'\n").getBytes("UTF-8"));
STDIN.write("exit\n".getBytes("UTF-8")); rootSTDIN.flush();
STDIN.flush();
process.waitFor();
try {
STDIN.close();
} catch (IOException ignore) {
// might be closed already
}
STDOUT.join(); STDOUT.join();
process.destroy(); } catch (InterruptedException | IOException e) {
} else { e.printStackTrace();
STDIN.write(("echo\n").getBytes("UTF-8")); rootShell.destroy();
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;
}
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;
}
}.exec();
} }
} }

View File

@ -1,5 +1,7 @@
package com.topjohnwu.magisk.utils; package com.topjohnwu.magisk.utils;
import android.text.TextUtils;
import java.io.BufferedReader; import java.io.BufferedReader;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -43,11 +45,11 @@ public class StreamGobbler extends Thread {
try { try {
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
if (TextUtils.equals(line, "-root-done-"))
return;
writer.add(line); writer.add(line);
if (!line.equals("-root-done-") && !line.isEmpty()) {
Logger.shell(isRoot, "OUT: " + line); Logger.shell(isRoot, "OUT: " + line);
} }
}
} catch (IOException e) { } catch (IOException e) {
// reader probably closed, expected exit condition // 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 MAGISK_UPDATE_NOTIFICATION_ID = 1;
private static final int APK_UPDATE_NOTIFICATION_ID = 2; 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"; 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)); 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 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"; 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"; 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\""; 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; List<String> ret;
String command = "cat " + path; String command = "cat " + path;
if (Shell.rootAccess()) { if (Shell.rootAccess()) {
ret = Shell.su(command); ret = shell.su(command);
} else { } else {
ret = Shell.sh(command); ret = Shell.sh(command);
} }
@ -114,7 +114,7 @@ public class Utils {
.replace("#", "").replace("@", "").replace("*", ""); .replace("#", "").replace("@", "").replace("*", "");
} }
public static String detectBootImage() { public static String detectBootImage(Shell shell) {
String[] commands = { String[] commands = {
"for PARTITION in kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do", "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 || " + "BOOTIMAGE=`readlink /dev/block/by-name/$PARTITION || " +
@ -124,7 +124,7 @@ public class Utils {
"done", "done",
"echo \"$BOOTIMAGE\"" "echo \"$BOOTIMAGE\""
}; };
List<String> ret = Shell.su(commands); List<String> ret = shell.su(commands);
if (isValidShellResponse(ret)) { if (isValidShellResponse(ret)) {
return ret.get(0); return ret.get(0);
} }