You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
Rewrite root shell
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user