Merge shells

This commit is contained in:
topjohnwu 2017-07-18 03:34:06 +08:00
parent 2ea046cd80
commit 6933bcf7bb
14 changed files with 188 additions and 255 deletions

View File

@ -35,7 +35,7 @@ public class FlashActivity extends Activity {
@OnClick(R.id.reboot)
public void reboot() {
Shell.getRootShell(this).su_raw("reboot");
Shell.getShell(this).su_raw("reboot");
}
@Override

View File

@ -26,7 +26,6 @@ import android.widget.Spinner;
import android.widget.TextView;
import com.topjohnwu.magisk.asyncs.CheckUpdates;
import com.topjohnwu.magisk.asyncs.ParallelTask;
import com.topjohnwu.magisk.components.AlertDialogBuilder;
import com.topjohnwu.magisk.components.Fragment;
import com.topjohnwu.magisk.components.SnackbarMaker;
@ -105,25 +104,18 @@ public class MagiskFragment extends Fragment
collapse();
}
@OnClick(R.id.detect_bootimage)
public void toAutoDetect() {
if (magiskManager.bootBlock != null) {
spinner.setSelection(0);
}
}
@OnClick(R.id.install_button)
public void install() {
String bootImage = null;
if (magiskManager.blockList != null) {
int idx = spinner.getSelectedItemPosition();
if (Shell.rootAccess()) {
if (magiskManager.bootBlock != null) {
bootImage = magiskManager.bootBlock;
} else {
int idx = spinner.getSelectedItemPosition();
if (idx > 0) {
bootImage = magiskManager.blockList.get(idx - 1);
} else {
SnackbarMaker.make(getActivity(), R.string.manual_boot_image, Snackbar.LENGTH_LONG);
SnackbarMaker.make(getActivity(), R.string.manual_boot_image, Snackbar.LENGTH_LONG).show();
return;
}
}
@ -135,7 +127,8 @@ public class MagiskFragment extends Fragment
.setMessage(getString(R.string.repo_install_msg, filename))
.setCancelable(true)
.setPositiveButton(Shell.rootAccess() ? R.string.install : R.string.download,
(dialogInterface, i) -> Utils.dlAndReceive(
(d, i) ->
Utils.dlAndReceive(
getActivity(),
new DownloadReceiver() {
private String boot = finalBootImage;
@ -145,9 +138,13 @@ public class MagiskFragment extends Fragment
@Override
public void onDownloadDone(Uri uri, Context context) {
if (Shell.rootAccess()) {
new SetInstallFlags(boot, enc, verity)
.setCallBack(() -> startActivity(new Intent(context, FlashActivity.class).setData(uri)))
.exec(context);
magiskManager.shell.su_raw(
"rm -f /dev/.magisk",
"echo \"BOOTIMAGE=" + boot + "\" >> /dev/.magisk",
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(enc) + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + String.valueOf(verity) + "\" >> /dev/.magisk"
);
startActivity(new Intent(context, FlashActivity.class).setData(uri));
} else {
Utils.showUriSnack(getActivity(), uri);
}
@ -155,7 +152,7 @@ public class MagiskFragment extends Fragment
},
magiskManager.magiskLink,
Utils.getLegalFilename(filename)))
.setNeutralButton(R.string.release_notes, (dialog, which) -> {
.setNeutralButton(R.string.release_notes, (d, i) -> {
if (magiskManager.releaseNoteLink != null) {
Intent openReleaseNoteLink = new Intent(Intent.ACTION_VIEW, Uri.parse(magiskManager.releaseNoteLink));
openReleaseNoteLink.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@ -166,28 +163,6 @@ public class MagiskFragment extends Fragment
.show();
}
private static class SetInstallFlags extends ParallelTask<Context, Void, Void> {
private String boot;
private boolean enc, verity;
SetInstallFlags(String boot, boolean enc, boolean verity) {
this.boot = boot;
this.enc = enc;
this.verity = verity;
}
@Override
protected Void doInBackground(Context... contexts) {
Shell.getRootShell(contexts[0]).su_raw("rm -f /dev/.magisk",
(boot != null) ? "echo \"BOOTIMAGE=" + boot + "\" >> /dev/.magisk" : "",
"echo \"KEEPFORCEENCRYPT=" + String.valueOf(enc) + "\" >> /dev/.magisk",
"echo \"KEEPVERITY=" + String.valueOf(verity) + "\" >> /dev/.magisk"
);
return null;
}
}
@OnClick(R.id.uninstall_button)
public void uninstall() {
new AlertDialogBuilder(getActivity())
@ -225,7 +200,7 @@ public class MagiskFragment extends Fragment
@Override
public void onFinish() {
progress.setMessage(getString(R.string.reboot_countdown, 0));
magiskManager.rootShell.su_raw(
magiskManager.shell.su_raw(
"mv -f " + uninstaller + " /cache/" + MagiskManager.UNINSTALLER,
"mv -f " + utils + " /data/magisk/" + MagiskManager.UTIL_FUNCTIONS,
"reboot"
@ -314,8 +289,6 @@ public class MagiskFragment extends Fragment
updateCheckUI();
} else if (event == magiskManager.safetyNetDone) {
updateSafetyNetUI();
} else if (event == magiskManager.blockDetectionDone) {
updateInstallUI();
}
}
@ -327,11 +300,8 @@ public class MagiskFragment extends Fragment
updateCheckUI();
if (magiskManager.safetyNetDone.isTriggered)
updateSafetyNetUI();
if (magiskManager.blockDetectionDone.isTriggered || !Shell.rootAccess())
updateInstallUI();
magiskManager.updateCheckDone.register(this);
magiskManager.safetyNetDone.register(this);
magiskManager.blockDetectionDone.register(this);
getActivity().setTitle(R.string.magisk);
}
@ -339,7 +309,6 @@ public class MagiskFragment extends Fragment
public void onStop() {
magiskManager.updateCheckDone.unRegister(this);
magiskManager.safetyNetDone.unRegister(this);
magiskManager.blockDetectionDone.unRegister(this);
super.onStop();
}
@ -351,6 +320,8 @@ public class MagiskFragment extends Fragment
private void updateUI() {
((MainActivity) getActivity()).checkHideSection();
magiskManager.updateMagiskInfo();
final int ROOT = 0x1, NETWORK = 0x2, UPTODATE = 0x4;
int status = 0;
status |= Shell.rootAccess() ? ROOT : 0;
@ -362,14 +333,9 @@ public class MagiskFragment extends Fragment
installOptionCard.setVisibility(Utils.checkBits(status, NETWORK, ROOT) ? View.VISIBLE : View.GONE);
installButton.setVisibility(Utils.checkBits(status, NETWORK) ? View.VISIBLE : View.GONE);
uninstallButton.setVisibility(Utils.checkBits(status, UPTODATE, ROOT) ? View.VISIBLE : View.GONE);
updateVersionUI();
}
private void updateVersionUI() {
int image, color;
magiskManager.updateMagiskInfo();
if (magiskManager.magiskVersionCode < 0) {
color = colorBad;
image = R.drawable.ic_cancel;
@ -405,6 +371,25 @@ public class MagiskFragment extends Fragment
rootStatusIcon.setImageResource(image);
rootStatusIcon.setColorFilter(color);
if (!Shell.rootAccess()) {
installText.setText(R.string.download);
} else {
installText.setText(R.string.download_install);
List<String> items = new ArrayList<>();
if (magiskManager.bootBlock != null) {
items.add(getString(R.string.auto_detect, magiskManager.bootBlock));
spinner.setEnabled(false);
} else {
items.add(getString(R.string.cannot_auto_detect));
items.addAll(magiskManager.blockList);
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
}
}
private void updateCheckUI() {
@ -428,28 +413,6 @@ public class MagiskFragment extends Fragment
mSwipeRefreshLayout.setRefreshing(false);
}
private void updateInstallUI() {
if (!Shell.rootAccess()) {
installText.setText(R.string.download);
} else {
installText.setText(R.string.download_install);
List<String> items = new ArrayList<>();
if (magiskManager.bootBlock != null) {
items.add(getString(R.string.auto_detect, magiskManager.bootBlock));
spinner.setEnabled(false);
} else {
items.add(getString(R.string.cannot_auto_detect));
items.addAll(magiskManager.blockList);
}
ArrayAdapter<String> adapter = new ArrayAdapter<>(getActivity(),
android.R.layout.simple_spinner_item, items);
adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
spinner.setAdapter(adapter);
toAutoDetect();
}
}
private void updateSafetyNetUI() {
int image, color;
safetyNetProgress.setVisibility(View.GONE);

View File

@ -142,7 +142,7 @@ public class MagiskLogFragment extends Fragment {
mode = (int) params[0];
switch (mode) {
case 0:
List<String> logList = Utils.readFile(magiskManager.rootShell, MAGISK_LOG);
List<String> logList = Utils.readFile(magiskManager.shell, MAGISK_LOG);
if (Utils.isValidShellResponse(logList)) {
StringBuilder llog = new StringBuilder(15 * 10 * 1024);
@ -154,7 +154,7 @@ public class MagiskLogFragment extends Fragment {
return "";
case 1:
magiskManager.rootShell.su_raw("echo > " + MAGISK_LOG);
magiskManager.shell.su_raw("echo > " + MAGISK_LOG);
SnackbarMaker.make(txtLog, R.string.logs_cleared, Snackbar.LENGTH_SHORT).show();
return "";
@ -184,7 +184,7 @@ public class MagiskLogFragment extends Fragment {
return false;
}
List<String> in = Utils.readFile(magiskManager.rootShell, MAGISK_LOG);
List<String> in = Utils.readFile(magiskManager.shell, MAGISK_LOG);
if (Utils.isValidShellResponse(in)) {
try (FileWriter out = new FileWriter(targetFile)) {

View File

@ -38,7 +38,6 @@ public class MagiskManager extends Application {
public static final String NOTIFICATION_CHANNEL = "magisk_update_notice";
// Events
public final CallbackEvent<Void> blockDetectionDone = new CallbackEvent<>();
public final CallbackEvent<Void> magiskHideDone = new CallbackEvent<>();
public final CallbackEvent<Void> reloadMainActivity = new CallbackEvent<>();
public final CallbackEvent<Void> moduleLoadDone = new CallbackEvent<>();
@ -88,7 +87,7 @@ public class MagiskManager extends Application {
// Global resources
public SharedPreferences prefs;
public SuDatabaseHelper suDB;
public Shell rootShell;
public Shell shell;
private static Handler mHandler = new Handler();
@ -97,7 +96,7 @@ public class MagiskManager extends Application {
super.onCreate();
new File(getApplicationInfo().dataDir).mkdirs(); /* Create the app data directory */
prefs = PreferenceManager.getDefaultSharedPreferences(this);
rootShell = Shell.getRootShell();
shell = Shell.getShell();
}
public void toast(String msg, int duration) {
@ -121,11 +120,12 @@ public class MagiskManager extends Application {
updateNotification = prefs.getBoolean("notification", true);
initSU();
updateMagiskInfo();
updateBlockInfo();
// Initialize busybox
File busybox = new File(getApplicationInfo().dataDir + "/busybox/busybox");
if (!busybox.exists() || !TextUtils.equals(prefs.getString("busybox_version", ""), BUSYBOX_VERSION)) {
busybox.getParentFile().mkdirs();
rootShell.su_raw(
shell.su_raw(
"cp -f " + new File(getApplicationInfo().nativeLibraryDir, "libbusybox.so") + " " + busybox,
"chmod -R 755 " + busybox.getParent(),
busybox + " --install -s " + busybox.getParent()
@ -137,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(rootShell, MAGISK_DISABLE_FILE))
.putBoolean("disable", Utils.itemExist(shell, MAGISK_DISABLE_FILE))
.putBoolean("su_reauth", suReauth)
.putString("su_request_timeout", String.valueOf(suRequestTimeout))
.putString("su_auto_response", String.valueOf(suResponseType))
@ -148,7 +148,7 @@ public class MagiskManager extends Application {
.putString("busybox_version", BUSYBOX_VERSION)
.apply();
// Add busybox to PATH
rootShell.su_raw("PATH=$PATH:" + busybox.getParent());
shell.su_raw("PATH=$PATH:" + busybox.getParent());
// Create notification channel on Android O
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@ -170,7 +170,7 @@ public class MagiskManager extends Application {
public void initSU() {
initSUConfig();
List<String> ret = Shell.sh("su -v");
List<String> ret = shell.sh("su -v");
if (Utils.isValidShellResponse(ret)) {
suVersion = ret.get(0);
isSuClient = suVersion.toUpperCase().contains("MAGISK");
@ -184,9 +184,9 @@ public class MagiskManager extends Application {
public void updateMagiskInfo() {
List<String> ret;
ret = Shell.sh("magisk -v");
ret = shell.sh("magisk -v");
if (!Utils.isValidShellResponse(ret)) {
ret = Shell.sh("getprop magisk.version");
ret = shell.sh("getprop magisk.version");
if (Utils.isValidShellResponse(ret)) {
try {
magiskVersionString = ret.get(0);
@ -195,24 +195,39 @@ public class MagiskManager extends Application {
}
} else {
magiskVersionString = ret.get(0).split(":")[0];
ret = Shell.sh("magisk -V");
ret = shell.sh("magisk -V");
try {
magiskVersionCode = Integer.parseInt(ret.get(0));
} catch (NumberFormatException ignored) {}
}
ret = Shell.sh("getprop " + DISABLE_INDICATION_PROP);
ret = shell.sh("getprop " + DISABLE_INDICATION_PROP);
try {
disabled = Utils.isValidShellResponse(ret) && Integer.parseInt(ret.get(0)) != 0;
} catch (NumberFormatException e) {
disabled = false;
}
ret = Shell.sh("getprop " + MAGISKHIDE_PROP);
ret = shell.sh("getprop " + MAGISKHIDE_PROP);
try {
magiskHide = !Utils.isValidShellResponse(ret) || Integer.parseInt(ret.get(0)) != 0;
} catch (NumberFormatException e) {
magiskHide = true;
}
}
public void updateBlockInfo() {
List<String> res = shell.su(
"for BLOCK in boot_a BOOT_A kern-a KERN-A android_boot ANDROID_BOOT kernel KERNEL boot BOOT lnx LNX; do",
"BOOTIMAGE=`ls /dev/block/by-name/$BLOCK || ls /dev/block/platform/*/by-name/$BLOCK || ls /dev/block/platform/*/*/by-name/$BLOCK` 2>/dev/null",
"[ ! -z \"$BOOTIMAGE\" ] && break",
"done",
"[ ! -z \"$BOOTIMAGE\" -a -L \"$BOOTIMAGE\" ] && BOOTIMAGE=`readlink $BOOTIMAGE`",
"echo \"$BOOTIMAGE\""
);
if (Utils.isValidShellResponse(res)) {
bootBlock = res.get(0);
} else {
blockList = shell.su("ls -d /dev/block/mmc* /dev/block/sd* 2>/dev/null");
}
}
}

View File

@ -149,9 +149,9 @@ public class SettingsActivity extends Activity {
case "disable":
enabled = prefs.getBoolean("disable", false);
if (enabled) {
Utils.createFile(magiskManager.rootShell, MagiskManager.MAGISK_DISABLE_FILE);
Utils.createFile(magiskManager.shell, MagiskManager.MAGISK_DISABLE_FILE);
} else {
Utils.removeItem(magiskManager.rootShell, MagiskManager.MAGISK_DISABLE_FILE);
Utils.removeItem(magiskManager.shell, 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) {
magiskManager.rootShell.su_raw(
magiskManager.shell.su_raw(
"cp -af /system/etc/hosts /magisk/.core/hosts",
"mount -o bind /magisk/.core/hosts /system/etc/hosts");
} else {
magiskManager.rootShell.su_raw(
magiskManager.shell.su_raw(
"umount -l /system/etc/hosts",
"rm -f /magisk/.core/hosts");
}

View File

@ -8,7 +8,6 @@ import android.content.Intent;
import android.os.Bundle;
import android.text.TextUtils;
import com.topjohnwu.magisk.asyncs.GetBootBlocks;
import com.topjohnwu.magisk.asyncs.LoadApps;
import com.topjohnwu.magisk.asyncs.LoadModules;
import com.topjohnwu.magisk.asyncs.LoadRepos;
@ -25,12 +24,13 @@ public class SplashActivity extends Activity{
super.onCreate(savedInstanceState);
// Init the info and configs and root shell
// Init the info and configs and root sh
getApplicationContext().init();
// Now fire all async tasks
new GetBootBlocks(this).exec();
new LoadModules(this).setCallBack(() -> new LoadRepos(this).exec()).exec();
new LoadModules(this)
.setCallBack(() -> new LoadRepos(this).exec())
.exec();
new LoadApps(this).exec();
if (Utils.checkNetworkStatus(this)) {

View File

@ -38,7 +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);
Shell rootShell = Shell.getShell(context);
final Module module = mList.get(position);
String version = module.getVersion();

View File

@ -63,7 +63,7 @@ public class FlashZip extends ParallelTask<Void, String, Integer> {
private boolean unzipAndCheck() throws Exception {
ZipUtils.unzip(mCachedFile, mCachedFile.getParentFile(), "META-INF/com/google/android");
List<String> ret = Utils.readFile(magiskManager.rootShell, mCheckFile.getPath());
List<String> ret = Utils.readFile(magiskManager.shell, mCheckFile.getPath());
return Utils.isValidShellResponse(ret) && ret.get(0).contains("#MAGISK");
}
@ -84,7 +84,7 @@ public class FlashZip extends ParallelTask<Void, String, Integer> {
copyToCache();
if (!unzipAndCheck()) return 0;
mList.add(magiskManager.getString(R.string.zip_install_progress_msg, mFilename));
magiskManager.rootShell.su(mList,
magiskManager.shell.su(mList,
"BOOTMODE=true sh " + mScriptFile + " dummy 1 " + mCachedFile +
" && echo 'Success!' || echo 'Failed!'"
);
@ -99,7 +99,7 @@ public class FlashZip extends ParallelTask<Void, String, Integer> {
// -1 = error, manual install; 0 = invalid zip; 1 = success
@Override
protected void onPostExecute(Integer result) {
magiskManager.rootShell.su_raw(
magiskManager.shell.su_raw(
"rm -rf " + mCachedFile.getParent() + "/*",
"rm -rf " + MagiskManager.TMP_FOLDER_PATH
);

View File

@ -1,29 +0,0 @@
package com.topjohnwu.magisk.asyncs;
import android.app.Activity;
import com.topjohnwu.magisk.utils.Utils;
public class GetBootBlocks extends ParallelTask<Void, Void, Void> {
public GetBootBlocks(Activity context) {
super(context);
}
@Override
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.rootShell);
}
return null;
}
@Override
protected void onPostExecute(Void v) {
magiskManager.blockDetectionDone.trigger();
super.onPostExecute(v);
}
}

View File

@ -21,10 +21,10 @@ public class LoadModules extends ParallelTask<Void, Void, Void> {
magiskManager.moduleMap = new ValueSortedMap<>();
for (String path : Utils.getModList(magiskManager.rootShell, MagiskManager.MAGISK_PATH)) {
for (String path : Utils.getModList(magiskManager.shell, MagiskManager.MAGISK_PATH)) {
Logger.dev("LoadModules: Adding modules from " + path);
try {
Module module = new Module(magiskManager.rootShell, path);
Module module = new Module(magiskManager.shell, path);
magiskManager.moduleMap.put(module.getId(), module);
} catch (BaseModule.CacheModException ignored) {}
}

View File

@ -15,7 +15,7 @@ public class MagiskHide extends ParallelTask<Object, Void, Void> {
@Override
protected Void doInBackground(Object... params) {
String command = (String) params[0];
List<String> ret = magiskManager.rootShell.su("magiskhide --" + command);
List<String> ret = magiskManager.shell.su("magiskhide --" + command);
if (isList) {
magiskManager.magiskHideList = ret;
}

View File

@ -37,13 +37,13 @@ public class Logger {
dev(String.format(Locale.US, fmt, args));
}
public static void shell(boolean root, String line) {
public static void shell(String line) {
if (MagiskManager.shellLogging) {
Log.d(DEBUG_TAG, (root ? "MANAGERSU: " : "MANAGERSH: ") + line);
Log.d(DEBUG_TAG, "SHELL: " + line);
}
}
public static void shell(boolean root, String fmt, Object... args) {
shell(root, String.format(Locale.US, fmt, args));
public static void shell(String fmt, Object... args) {
shell(String.format(Locale.US, fmt, args));
}
}

View File

@ -2,11 +2,12 @@ package com.topjohnwu.magisk.utils;
import android.content.Context;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
@ -18,145 +19,133 @@ public class Shell {
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted
public static int rootStatus;
private final Process rootShell;
private final DataOutputStream rootSTDIN;
private final DataInputStream rootSTDOUT;
private final Process shellProcess;
private final DataOutputStream STDIN;
private final DataInputStream STDOUT;
private boolean isValid;
private Shell() {
Process process;
rootStatus = 1;
Process process = null;
DataOutputStream in = null;
DataInputStream out = null;
try {
process = Runtime.getRuntime().exec("su");
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
} catch (IOException e) {
// No root
rootStatus = 0;
rootShell = null;
rootSTDIN = null;
rootSTDOUT = null;
}
while (true) {
if (rootAccess()) {
try {
in.write(("id\n").getBytes("UTF-8"));
in.flush();
String s = new BufferedReader(new InputStreamReader(out)).readLine();
if (s.isEmpty() || !s.contains("uid=0")) {
in.close();
out.close();
process.destroy();
throw new IOException();
}
} catch (IOException e) {
rootStatus = -1;
continue;
}
break;
} else {
// Try to gain non-root sh
try {
process = Runtime.getRuntime().exec("sh");
in = new DataOutputStream(process.getOutputStream());
out = new DataInputStream(process.getInputStream());
} catch (IOException e) {
// Nothing works....
shellProcess = null;
STDIN = null;
STDOUT = null;
isValid = false;
return;
}
break;
}
}
rootStatus = 1;
isValid = true;
rootShell = process;
rootSTDIN = new DataOutputStream(rootShell.getOutputStream());
rootSTDOUT = new DataInputStream(rootShell.getInputStream());
su_raw("umask 022");
List<String> ret = su("echo -BOC-", "id");
if (ret.isEmpty()) {
// Something wrong with root, not allowed?
rootStatus = -1;
return;
shellProcess = process;
STDIN = in;
STDOUT = out;
sh_raw("umask 022");
}
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") ? 1 : -1;
return;
} else if (!line.contains("-BOC-")) {
rootStatus = -1;
return;
}
}
}
public static Shell getRootShell() {
public static Shell getShell() {
return new Shell();
}
public static Shell getRootShell(Context context) {
return Utils.getMagiskManager(context).rootShell;
public static Shell getShell(Context context) {
return Utils.getMagiskManager(context).shell;
}
public static boolean rootAccess() {
return rootStatus > 0;
}
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();
Logger.shell(false, write);
}
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 List<String> su(String... commands) {
if (!isValid) return null;
public List<String> sh(String... commands) {
List<String> res = new ArrayList<>();
su(res, commands);
if (!isValid) return res;
sh(res, commands);
return res;
}
public void su_raw(String... commands) {
public void sh_raw(String... commands) {
if (!isValid) return;
synchronized (rootShell) {
synchronized (shellProcess) {
try {
for (String command : commands) {
rootSTDIN.write((command + "\n").getBytes("UTF-8"));
rootSTDIN.flush();
STDIN.write((command + "\n").getBytes("UTF-8"));
STDIN.flush();
}
} catch (IOException e) {
e.printStackTrace();
rootShell.destroy();
shellProcess.destroy();
isValid = false;
}
}
}
public void su(List<String> output, String... commands) {
public void sh(List<String> output, String... commands) {
if (!isValid) return;
try {
rootShell.exitValue();
shellProcess.exitValue();
isValid = false;
return; // The process is dead, return
} catch (IllegalThreadStateException ignored) {
// This should be the expected result
}
synchronized (rootShell) {
StreamGobbler STDOUT = new StreamGobbler(rootSTDOUT, output, true);
STDOUT.start();
su_raw(commands);
su_raw("echo \'-root-done-\'");
try { STDOUT.join(); } catch (InterruptedException ignored) {}
synchronized (shellProcess) {
StreamGobbler out = new StreamGobbler(this.STDOUT, output);
out.start();
sh_raw(commands);
sh_raw("echo \'-shell-done-\'");
try { out.join(); } catch (InterruptedException ignored) {}
}
}
public List<String> su(String... commands) {
if (!rootAccess()) return sh();
return sh(commands);
}
public void su_raw(String... commands) {
if (!rootAccess()) return;
sh_raw(commands);
}
public void su(List<String> output, String... commands) {
if (!rootAccess()) return;
sh(output, commands);
}
}

View File

@ -21,7 +21,7 @@ public class StreamGobbler extends Thread {
/**
* <p>StreamGobbler constructor</p>
*
* <p>We use this class because shell STDOUT and STDERR should be read as quickly as
* <p>We use this class because sh 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>
*
@ -38,21 +38,16 @@ public class StreamGobbler extends Thread {
writer = outputList;
}
public StreamGobbler(InputStream inputStream, List<String> outputList, boolean root) {
this(inputStream, outputList);
isRoot = root;
}
@Override
public void run() {
// keep reading the InputStream until it ends (or an error occurs)
try {
String line;
while ((line = reader.readLine()) != null) {
if (TextUtils.equals(line, "-root-done-"))
if (TextUtils.equals(line, "-shell-done-"))
return;
writer.add(line);
Logger.shell(isRoot, line);
Logger.shell(line);
}
} catch (IOException e) {
// reader probably closed, expected exit condition