You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
Rewrite all root method with own su library
This commit is contained in:
@@ -0,0 +1,172 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
||||
*/
|
||||
|
||||
public class Shell {
|
||||
|
||||
|
||||
public static String suPath;
|
||||
// -1 = problematic/unknown issue; 0 = not rooted; 1 = properly rooted; 2 = improperly rooted;
|
||||
public static int rootStatus = 0;
|
||||
|
||||
private static Process rootShell;
|
||||
private static DataOutputStream rootSTDIN;
|
||||
private static StreamGobbler rootSTDOUT;
|
||||
private static List<String> rootOutList = new ArrayList<>();
|
||||
|
||||
static {
|
||||
List<String> ret = sh("getprop magisk.supath");
|
||||
if(!ret.isEmpty()) {
|
||||
suPath = ret.get(0) + "/su";
|
||||
} else {
|
||||
suPath = "su";
|
||||
rootStatus = 2;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean rootAccess() {
|
||||
return rootStatus > 0;
|
||||
}
|
||||
|
||||
public static void startRoot() {
|
||||
rootStatus = rootStatus == 0 ? 1 : 2;
|
||||
|
||||
try {
|
||||
rootShell = Runtime.getRuntime().exec(suPath);
|
||||
rootSTDIN = new DataOutputStream(rootShell.getOutputStream());
|
||||
rootSTDOUT = new StreamGobbler(rootShell.getInputStream(), rootOutList);
|
||||
rootSTDOUT.start();
|
||||
} catch (IOException e) {
|
||||
// runtime error! No binary found!
|
||||
rootStatus = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
List<String> ret = su("echo -BOC-", "id");
|
||||
if (ret == null) {
|
||||
// Something wrong with root install, not enabled?
|
||||
rootStatus = -1;
|
||||
return;
|
||||
}
|
||||
|
||||
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;
|
||||
return;
|
||||
} else if (!line.contains("-BOC-")) {
|
||||
rootStatus = -1;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void finalize() throws Throwable {
|
||||
super.finalize();
|
||||
if (rootAccess()) {
|
||||
rootSTDIN.write("exit\n".getBytes("UTF-8"));
|
||||
rootSTDIN.flush();
|
||||
rootSTDIN.flush();
|
||||
rootShell.waitFor();
|
||||
rootSTDIN.close();
|
||||
rootSTDOUT.join();
|
||||
rootShell.destroy();
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
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 static List<String> su(String... commands) {
|
||||
|
||||
if(!Shell.rootAccess()) return null;
|
||||
|
||||
rootOutList.clear();
|
||||
|
||||
try {
|
||||
try {
|
||||
for (String write : commands) {
|
||||
rootSTDIN.write((write + "\n").getBytes("UTF-8"));
|
||||
rootSTDIN.flush();
|
||||
}
|
||||
rootSTDIN.write(("echo \'-done-\'\n").getBytes("UTF-8"));
|
||||
rootSTDIN.flush();
|
||||
} catch (IOException e) {
|
||||
if (!e.getMessage().contains("EPIPE")) {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
while (true) {
|
||||
try {
|
||||
// Process terminated, it means the interactive shell cannot be initialized
|
||||
rootShell.exitValue();
|
||||
return null;
|
||||
} catch (IllegalThreadStateException e) {
|
||||
// Process still running, gobble output until done
|
||||
if (rootOutList != null && !rootOutList.isEmpty()) {
|
||||
if (rootOutList.get(rootOutList.size() - 1).equals("-done-")) {
|
||||
rootOutList.remove(rootOutList.size() - 1);
|
||||
break;
|
||||
}
|
||||
}
|
||||
rootSTDOUT.join(100);
|
||||
}
|
||||
}
|
||||
|
||||
} catch (IOException | InterruptedException e) {
|
||||
// shell probably not found
|
||||
return null;
|
||||
}
|
||||
|
||||
return new ArrayList<>(rootOutList);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Modified by topjohnwu, based on Chainfire's libsuperuser
|
||||
*/
|
||||
|
||||
public class StreamGobbler extends Thread {
|
||||
|
||||
private BufferedReader reader = null;
|
||||
private List<String> writer = null;
|
||||
|
||||
/**
|
||||
* <p>StreamGobbler constructor</p>
|
||||
*
|
||||
* <p>We use this class because shell 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>
|
||||
*
|
||||
* @param inputStream InputStream to read from
|
||||
* @param outputList {@literal List<String>} to write to, or null
|
||||
*/
|
||||
public StreamGobbler(InputStream inputStream, List<String> outputList) {
|
||||
reader = new BufferedReader(new InputStreamReader(inputStream));
|
||||
writer = outputList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
// keep reading the InputStream until it ends (or an error occurs)
|
||||
try {
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
writer.add(line);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
// reader probably closed, expected exit condition
|
||||
}
|
||||
|
||||
// make sure our stream is closed and resources will be freed
|
||||
try {
|
||||
reader.close();
|
||||
} catch (IOException e) {
|
||||
// read already closed
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,56 +1,82 @@
|
||||
package com.topjohnwu.magisk.utils;
|
||||
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import com.topjohnwu.magisk.module.Module;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import eu.chainfire.libsuperuser.Shell;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public static final String suPath = sh("getprop magisk.supath");
|
||||
public static boolean rootAccess = false;
|
||||
public static Init initialize;
|
||||
|
||||
public static String sh(String... commands) {
|
||||
List<String> result = Shell.SH.run(commands);
|
||||
public static List<Module> listModules = new ArrayList<>();
|
||||
public static List<Module> listModulesCache = new ArrayList<>();
|
||||
public static List<String> listLog;
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String s : result) {
|
||||
builder.append(s);
|
||||
}
|
||||
public static class Init extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static String su(String... commands) {
|
||||
List<String> result = Shell.run(Utils.suPath + "/su", commands, null, false);
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
for (String s : result) {
|
||||
builder.append(s);
|
||||
}
|
||||
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public static void checkRoot() {
|
||||
String [] availableTestCommands = new String[] {"echo -BOC-", "id"};
|
||||
List<String> ret = Shell.run(Utils.suPath + "/su", availableTestCommands, null, false);
|
||||
if (ret == null)
|
||||
return;
|
||||
|
||||
// Taken from libsuperuser
|
||||
|
||||
// this is only one of many ways this can be done
|
||||
|
||||
for (String line : ret) {
|
||||
if (line.contains("uid=")) {
|
||||
// id command is working, let's see if we are actually root
|
||||
rootAccess = line.contains("uid=0");
|
||||
} else if (line.contains("-BOC-")) {
|
||||
// if we end up here, at least the su command starts some kind
|
||||
// of shell, let's hope it has root privileges - no way to know without
|
||||
// additional native binaries
|
||||
rootAccess = true;
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
Shell.startRoot();
|
||||
listModules.clear();
|
||||
listModulesCache.clear();
|
||||
List<String> magisk = getModList("/magisk");
|
||||
List<String> magiskCache = getModList("/cache/magisk");
|
||||
if (!magisk.isEmpty()) {
|
||||
for (String mod : magisk) {
|
||||
listModules.add(new Module(mod));
|
||||
}
|
||||
}
|
||||
|
||||
if (!magiskCache.isEmpty()) {
|
||||
for (String mod : magiskCache) {
|
||||
listModulesCache.add(new Module(mod));
|
||||
}
|
||||
}
|
||||
listLog = readFile("/cache/magisk.log");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean fileExist(String path) {
|
||||
List<String> ret;
|
||||
ret = Shell.sh("if [ -f " + path + " ]; then echo true; else echo false; fi");
|
||||
if (!Boolean.parseBoolean(ret.get(0)) && Shell.rootAccess()) ret = Shell.su("if [ -f " + path + " ]; then echo true; else echo false; fi");
|
||||
return Boolean.parseBoolean(ret.get(0));
|
||||
}
|
||||
|
||||
public static boolean createFile(String path) {
|
||||
if (!Shell.rootAccess()) {
|
||||
return false;
|
||||
} else {
|
||||
return Boolean.parseBoolean(Shell.su("touch " + path + " 2>/dev/null; if [ -f " + path + " ]; then echo true; else echo false; fi").get(0));
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean removeFile(String path) {
|
||||
if (!Shell.rootAccess()) {
|
||||
return false;
|
||||
} else {
|
||||
return Boolean.parseBoolean(Shell.su("rm -f " + path + " 2>/dev/null; if [ -f " + path + " ]; then echo false; else echo true; fi").get(0));
|
||||
}
|
||||
}
|
||||
|
||||
public static List<String> getModList(String path) {
|
||||
List<String> ret;
|
||||
ret = Shell.sh("find " + path + " -type d -maxdepth 1 | while read ITEM ; do if [ -f $ITEM/module.prop ]; then echo $ITEM; fi; done");
|
||||
if (ret.isEmpty() && Shell.rootAccess()) ret = Shell.su("find " + path + " -type d -maxdepth 1 | while read ITEM ; do if [ -f $ITEM/module.prop ]; then echo $ITEM; fi; done");
|
||||
return ret;
|
||||
}
|
||||
|
||||
public static List<String> readFile(String path) {
|
||||
List<String> ret;
|
||||
ret = Shell.sh("cat " + path);
|
||||
if (ret.isEmpty() && Shell.rootAccess()) ret = Shell.su("cat " + path, "echo \' \'");
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user