Revert "manager: Optimized import, optimized all libsu shell calls, and fixed WebUI memory leaks #369

This reverts commit c3533861f2.
This commit is contained in:
ShirkNeko
2025-09-13 20:18:30 +08:00
parent 0c578e4518
commit cead5b03f4
9 changed files with 253 additions and 166 deletions

View File

@@ -12,8 +12,6 @@ import android.os.Bundle
import coil.Coil
import coil.ImageLoader
import com.dergoogler.mmrl.platform.Platform
import com.sukisu.ultra.ui.util.createRootShellBuilder
import com.topjohnwu.superuser.Shell
import me.zhanghai.android.appiconloader.coil.AppIconFetcher
import me.zhanghai.android.appiconloader.coil.AppIconKeyer
import java.io.File
@@ -88,8 +86,6 @@ class KernelSUApplication : Application() {
override fun onCreate() {
super.onCreate()
ksuApp = this
Shell.setDefaultBuilder(createRootShellBuilder(true))
Shell.enableVerboseLogging = BuildConfig.DEBUG
// 注册Activity生命周期回调
registerActivityLifecycleCallbacks(activityLifecycleCallbacks)

View File

@@ -1,6 +1,5 @@
package com.sukisu.ultra.ui.screen
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.util.Log
@@ -18,27 +17,25 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import androidx.core.content.edit
import androidx.lifecycle.viewmodel.compose.viewModel
import com.ramcosta.composedestinations.annotation.Destination
import com.ramcosta.composedestinations.annotation.RootGraph
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.component.*
import com.sukisu.ultra.ui.theme.getCardColors
import com.sukisu.ultra.ui.theme.getCardElevation
import com.sukisu.ultra.ui.util.loadKpmModule
import com.sukisu.ultra.ui.util.unloadKpmModule
import com.sukisu.ultra.ui.viewmodel.KpmViewModel
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import com.sukisu.ultra.ui.component.*
import com.sukisu.ultra.ui.theme.*
import com.sukisu.ultra.ui.viewmodel.KpmViewModel
import com.sukisu.ultra.ui.util.*
import java.io.File
import androidx.core.content.edit
import com.sukisu.ultra.R
import java.io.FileInputStream
import java.net.URLEncoder
import java.net.*
import android.app.Activity
import androidx.compose.ui.res.painterResource
/**
* KPM 管理界面
@@ -86,8 +83,9 @@ fun KpmScreen(
LaunchedEffect(tempFileForInstall) {
tempFileForInstall?.let { tempFile ->
try {
val shell = getRootShell()
val command = "strings ${tempFile.absolutePath} | grep 'name='"
val result = Shell.cmd(command).to(ArrayList(), null).exec()
val result = shell.newJob().add(command).to(ArrayList(), null).exec()
if (result.isSuccess) {
for (line in result.out) {
if (line.startsWith("name=")) {
@@ -426,8 +424,9 @@ private suspend fun handleModuleInstall(
) {
var moduleId: String? = null
try {
val shell = getRootShell()
val command = "strings ${tempFile.absolutePath} | grep 'name='"
val result = Shell.cmd(command).to(ArrayList(), null).exec()
val result = shell.newJob().add(command).to(ArrayList(), null).exec()
if (result.isSuccess) {
for (line in result.out) {
if (line.startsWith("name=")) {
@@ -454,8 +453,9 @@ private suspend fun handleModuleInstall(
try {
if (isEmbed) {
Shell.cmd("mkdir -p /data/adb/kpm").exec()
Shell.cmd("cp ${tempFile.absolutePath} $targetPath").exec()
val shell = getRootShell()
shell.newJob().add("mkdir -p /data/adb/kpm").exec()
shell.newJob().add("cp ${tempFile.absolutePath} $targetPath").exec()
}
val loadResult = loadKpmModule(tempFile.absolutePath)
@@ -499,7 +499,8 @@ private suspend fun handleModuleUninstall(
val moduleFilePath = "/data/adb/kpm/$moduleFileName"
val fileExists = try {
val result = Shell.cmd("ls /data/adb/kpm/$moduleFileName").exec()
val shell = getRootShell()
val result = shell.newJob().add("ls /data/adb/kpm/$moduleFileName").exec()
result.isSuccess
} catch (e: Exception) {
Log.e("KsuCli", "Failed to check module file existence: ${e.message}", e)
@@ -530,7 +531,8 @@ private suspend fun handleModuleUninstall(
}
if (fileExists) {
Shell.cmd("rm $moduleFilePath").exec()
val shell = getRootShell()
shell.newJob().add("rm $moduleFilePath").exec()
}
viewModel.fetchModuleList()
@@ -701,8 +703,9 @@ private fun KpmModuleItem(
}
private fun checkStringsCommand(tempFile: File): Int {
val shell = getRootShell()
val command = "strings ${tempFile.absolutePath} | grep -E 'name=|version=|license=|author='"
val result = Shell.cmd(command).to(ArrayList(), null).exec()
val result = shell.newJob().add(command).to(ArrayList(), null).exec()
if (!result.isSuccess) {
return 0

View File

@@ -10,15 +10,15 @@ import android.os.SystemClock
import android.provider.OpenableColumns
import android.system.Os
import android.util.Log
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import com.topjohnwu.superuser.CallbackList
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import com.topjohnwu.superuser.io.SuFile
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import kotlinx.parcelize.Parcelize
import com.sukisu.ultra.BuildConfig
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ksuApp
import org.json.JSONArray
import java.io.File
@@ -29,8 +29,19 @@ import java.io.File
*/
private const val TAG = "KsuCli"
private val ksuDaemonPath by lazy {
"${ksuApp.applicationInfo.nativeLibraryDir}${File.separator}libzakozako.so"
private fun getKsuDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozako.so"
}
object KsuCli {
val SHELL: Shell = createRootShell()
val GLOBAL_MNT_SHELL: Shell = createRootShell(true)
}
fun getRootShell(globalMnt: Boolean = false): Shell {
return if (globalMnt) KsuCli.GLOBAL_MNT_SHELL else {
KsuCli.SHELL
}
}
inline fun <T> withNewRootShell(
@@ -52,39 +63,37 @@ fun Uri.getFileName(context: Context): String? {
return fileName
}
fun createRootShellBuilder(globalMnt: Boolean = false): Shell.Builder {
return Shell.Builder.create().run {
val cmd = buildString {
append("$ksuDaemonPath debug su")
if (globalMnt) append(" -g")
append(" || ")
append("su")
if (globalMnt) append(" --mount-master")
append(" || ")
append("sh")
}
setCommands("sh", "-c", cmd)
}
}
fun createRootShell(globalMnt: Boolean = false): Shell {
return runCatching {
createRootShellBuilder(globalMnt).build()
}.getOrElse { e ->
Log.w(TAG, "su failed: ", e)
Shell.Builder.create().apply {
if (globalMnt) setFlags(Shell.FLAG_MOUNT_MASTER)
}.build()
Shell.enableVerboseLogging = BuildConfig.DEBUG
val builder = Shell.Builder.create()
return try {
if (globalMnt) {
builder.build(getKsuDaemonPath(), "debug", "su", "-g")
} else {
builder.build(getKsuDaemonPath(), "debug", "su")
}
} catch (e: Throwable) {
Log.w(TAG, "ksu failed: ", e)
try {
if (globalMnt) {
builder.build("su", "-mm")
} else {
builder.build("su")
}
} catch (e: Throwable) {
Log.e(TAG, "su failed: ", e)
builder.build("sh")
}
}
}
fun execKsud(args: String, newShell: Boolean = false): Boolean {
return if (newShell) {
withNewRootShell {
ShellUtils.fastCmdResult(this, "$ksuDaemonPath $args")
ShellUtils.fastCmdResult(this, "${getKsuDaemonPath()} $args")
}
} else {
ShellUtils.fastCmdResult("$ksuDaemonPath $args")
ShellUtils.fastCmdResult(getRootShell(), "${getKsuDaemonPath()} $args")
}
}
@@ -96,15 +105,19 @@ fun install() {
}
fun listModules(): String {
val shell = getRootShell()
val out =
Shell.cmd("$ksuDaemonPath module list").to(ArrayList(), null).exec().out
shell.newJob().add("${getKsuDaemonPath()} module list").to(ArrayList(), null).exec().out
return out.joinToString("\n").ifBlank { "[]" }
}
fun getModuleCount(): Int {
return runCatching {
JSONArray(listModules()).length()
}.getOrDefault(0)
val result = listModules()
runCatching {
val array = JSONArray(result)
return array.length()
}.getOrElse { return 0 }
}
fun getSuperuserCount(): Int {
@@ -172,7 +185,7 @@ fun flashModule(
this?.copyTo(output)
}
val cmd = "module install ${file.absolutePath}"
val result = flashWithIO("$ksuDaemonPath $cmd", onStdout, onStderr)
val result = flashWithIO("${getKsuDaemonPath()} $cmd", onStdout, onStderr)
Log.i("KernelSU", "install module $uri result: $result")
file.delete()
@@ -199,7 +212,7 @@ fun runModuleAction(
}
}
val result = shell.newJob().add("$ksuDaemonPath module action $moduleId")
val result = shell.newJob().add("${getKsuDaemonPath()} module action $moduleId")
.to(stdoutCallback, stderrCallback).exec()
Log.i("KernelSU", "Module runAction result: $result")
@@ -211,7 +224,7 @@ fun restoreBoot(
): Boolean {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so")
val result = flashWithIO(
"$ksuDaemonPath boot-restore -f --magiskboot $magiskboot",
"${getKsuDaemonPath()} boot-restore -f --magiskboot $magiskboot",
onStdout,
onStderr
)
@@ -224,7 +237,7 @@ fun uninstallPermanently(
): Boolean {
val magiskboot = File(ksuApp.applicationInfo.nativeLibraryDir, "libzakoboot.so")
val result =
flashWithIO("$ksuDaemonPath uninstall --magiskboot $magiskboot", onStdout, onStderr)
flashWithIO("${getKsuDaemonPath()} uninstall --magiskboot $magiskboot", onStdout, onStderr)
onFinish(result.isSuccess, result.code)
return result.isSuccess
}
@@ -299,7 +312,7 @@ fun installBoot(
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
cmd += " -o $downloadsDir"
val result = flashWithIO("$ksuDaemonPath $cmd", onStdout, onStderr)
val result = flashWithIO("${getKsuDaemonPath()} $cmd", onStdout, onStderr)
Log.i("KernelSU", "install boot result: ${result.isSuccess}")
bootFile?.delete()
@@ -311,17 +324,22 @@ fun installBoot(
}
fun reboot(reason: String = "") {
val shell = getRootShell()
if (reason == "recovery") {
// KEYCODE_POWER = 26, hide incorrect "Factory data reset" message
ShellUtils.fastCmdResult("/system/bin/input keyevent 26")
ShellUtils.fastCmd(shell, "/system/bin/input keyevent 26")
}
ShellUtils.fastCmdResult("/system/bin/svc power reboot $reason || /system/bin/reboot $reason")
ShellUtils.fastCmd(shell, "/system/bin/svc power reboot $reason || /system/bin/reboot $reason")
}
fun rootAvailable() = Shell.isAppGrantedRoot() == true
fun rootAvailable(): Boolean {
val shell = getRootShell()
return shell.isRoot
}
fun isAbDevice(): Boolean {
return ShellUtils.fastCmd("getprop ro.build.ab_update").trim().toBoolean()
val shell = getRootShell()
return ShellUtils.fastCmd(shell, "getprop ro.build.ab_update").trim().toBoolean()
}
fun isInitBoot(): Boolean {
@@ -329,77 +347,91 @@ fun isInitBoot(): Boolean {
}
suspend fun getCurrentKmi(): String = withContext(Dispatchers.IO) {
val shell = getRootShell()
val cmd = "boot-info current-kmi"
ShellUtils.fastCmd("$ksuDaemonPath $cmd")
ShellUtils.fastCmd(shell, "${getKsuDaemonPath()} $cmd")
}
suspend fun getSupportedKmis(): List<String> = withContext(Dispatchers.IO) {
val shell = getRootShell()
val cmd = "boot-info supported-kmi"
val out = Shell.cmd("$ksuDaemonPath $cmd").to(ArrayList(), null).exec().out
val out = shell.newJob().add("${getKsuDaemonPath()} $cmd").to(ArrayList(), null).exec().out
out.filter { it.isNotBlank() }.map { it.trim() }
}
fun hasMagisk(): Boolean {
val result = ShellUtils.fastCmdResult("which magisk")
Log.i(TAG, "has magisk: $result")
return result
val shell = getRootShell(true)
val result = shell.newJob().add("which magisk").exec()
Log.i(TAG, "has magisk: ${result.isSuccess}")
return result.isSuccess
}
fun isSepolicyValid(rules: String?): Boolean {
if (rules == null) {
return true
}
val shell = getRootShell()
val result =
Shell.cmd("$ksuDaemonPath sepolicy check '$rules'").to(ArrayList(), null)
shell.newJob().add("${getKsuDaemonPath()} sepolicy check '$rules'").to(ArrayList(), null)
.exec()
return result.isSuccess
}
fun getSepolicy(pkg: String): String {
val shell = getRootShell()
val result =
Shell.cmd("$ksuDaemonPath profile get-sepolicy $pkg").to(ArrayList(), null)
shell.newJob().add("${getKsuDaemonPath()} profile get-sepolicy $pkg").to(ArrayList(), null)
.exec()
Log.i(TAG, "code: ${result.code}, out: ${result.out}, err: ${result.err}")
return result.out.joinToString("\n")
}
fun setSepolicy(pkg: String, rules: String): Boolean {
val result = Shell.cmd("$ksuDaemonPath profile set-sepolicy $pkg '$rules'")
val shell = getRootShell()
val result = shell.newJob().add("${getKsuDaemonPath()} profile set-sepolicy $pkg '$rules'")
.to(ArrayList(), null).exec()
Log.i(TAG, "set sepolicy result: ${result.code}")
return result.isSuccess
}
fun listAppProfileTemplates(): List<String> {
return Shell.cmd("$ksuDaemonPath profile list-templates").to(ArrayList(), null)
val shell = getRootShell()
return shell.newJob().add("${getKsuDaemonPath()} profile list-templates").to(ArrayList(), null)
.exec().out
}
fun getAppProfileTemplate(id: String): String {
return Shell.cmd("$ksuDaemonPath profile get-template '${id}'")
val shell = getRootShell()
return shell.newJob().add("${getKsuDaemonPath()} profile get-template '${id}'")
.to(ArrayList(), null).exec().out.joinToString("\n")
}
fun setAppProfileTemplate(id: String, template: String): Boolean {
val shell = getRootShell()
val escapedTemplate = template.replace("\"", "\\\"")
val cmd = """$ksuDaemonPath profile set-template "$id" "$escapedTemplate'""""
return Shell.cmd(cmd)
val cmd = """${getKsuDaemonPath()} profile set-template "$id" "$escapedTemplate'""""
return shell.newJob().add(cmd)
.to(ArrayList(), null).exec().isSuccess
}
fun deleteAppProfileTemplate(id: String): Boolean {
return Shell.cmd("$ksuDaemonPath profile delete-template '${id}'")
val shell = getRootShell()
return shell.newJob().add("${getKsuDaemonPath()} profile delete-template '${id}'")
.to(ArrayList(), null).exec().isSuccess
}
fun forceStopApp(packageName: String) {
val result = Shell.cmd("am force-stop $packageName").exec()
val shell = getRootShell()
val result = shell.newJob().add("am force-stop $packageName").exec()
Log.i(TAG, "force stop $packageName result: $result")
}
fun launchApp(packageName: String) {
val shell = getRootShell()
val result =
Shell.cmd("cmd package resolve-activity --brief $packageName | tail -n 1 | xargs cmd activity start-activity -n")
shell.newJob()
.add("cmd package resolve-activity --brief $packageName | tail -n 1 | xargs cmd activity start-activity -n")
.exec()
Log.i(TAG, "launch $packageName result: $result")
}
@@ -409,90 +441,131 @@ fun restartApp(packageName: String) {
launchApp(packageName)
}
val suSFSDaemonPath by lazy {
"${ksuApp.applicationInfo.nativeLibraryDir}${File.separator}libzakozakozako.so"
fun getSuSFSDaemonPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libzakozakozako.so"
}
fun getSuSFS(): String {
return ShellUtils.fastCmd("$suSFSDaemonPath support")
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} support")
return result
}
fun getSuSFSVersion(): String {
return ShellUtils.fastCmd("$suSFSDaemonPath version")
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} version")
return result
}
fun getSuSFSVariant(): String {
return ShellUtils.fastCmd("$suSFSDaemonPath variant")
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} variant")
return result
}
fun getSuSFSFeatures(): String {
return ShellUtils.fastCmd("$suSFSDaemonPath features")
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} features")
return result
}
fun susfsSUS_SU_0(): String {
return ShellUtils.fastCmd("$suSFSDaemonPath sus_su 0")
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su 0")
return result
}
fun susfsSUS_SU_2(): String {
return ShellUtils.fastCmd("$suSFSDaemonPath sus_su 2")
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su 2")
return result
}
fun susfsSUS_SU_Mode(): String {
return ShellUtils.fastCmd("$suSFSDaemonPath sus_su mode")
val shell = getRootShell()
val result = ShellUtils.fastCmd(shell, "${getSuSFSDaemonPath()} sus_su mode")
return result
}
val kpmmgrPath by lazy {
"${ksuApp.applicationInfo.nativeLibraryDir}${File.separator}libkpmmgr.so"
fun getKpmmgrPath(): String {
return ksuApp.applicationInfo.nativeLibraryDir + File.separator + "libkpmmgr.so"
}
fun loadKpmModule(path: String, args: String? = null): String {
return ShellUtils.fastCmd("$kpmmgrPath load $path ${args ?: ""}")
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} load $path ${args ?: ""}"
return ShellUtils.fastCmd(shell, cmd)
}
fun unloadKpmModule(name: String): String {
return ShellUtils.fastCmd("$kpmmgrPath unload $name")
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} unload $name"
return ShellUtils.fastCmd(shell, cmd)
}
fun getKpmModuleCount(): Int {
val result = ShellUtils.fastCmd("$kpmmgrPath num")
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} num"
val result = ShellUtils.fastCmd(shell, cmd)
return result.trim().toIntOrNull() ?: 0
}
fun runCmd(cmd: String): String {
return Shell.cmd(cmd)
fun runCmd(shell: Shell, cmd: String): String {
return shell.newJob()
.add(cmd)
.to(mutableListOf<String>(), null)
.exec().out
.joinToString("\n")
}
fun listKpmModules(): String {
return runCmd("$kpmmgrPath list").trim()
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} list"
return try {
runCmd(shell, cmd).trim()
} catch (e: Exception) {
Log.e(TAG, "Failed to list KPM modules", e)
""
}
}
fun getKpmModuleInfo(name: String): String {
return runCmd("$kpmmgrPath info $name").trim()
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} info $name"
return try {
runCmd(shell, cmd).trim()
} catch (e: Exception) {
Log.e(TAG, "Failed to get KPM module info: $name", e)
""
}
}
fun controlKpmModule(name: String, args: String? = null): Int {
val result = runCmd("""$kpmmgrPath control $name "${args ?: ""}"""")
val shell = getRootShell()
val cmd = """${getKpmmgrPath()} control $name "${args ?: ""}""""
val result = runCmd(shell, cmd)
return result.trim().toIntOrNull() ?: -1
}
fun getKpmVersion(): String {
return ShellUtils.fastCmd("$kpmmgrPath version").trim()
val shell = getRootShell()
val cmd = "${getKpmmgrPath()} version"
val result = ShellUtils.fastCmd(shell, cmd)
return result.trim()
}
fun getZygiskImplement(): String {
val shell = getRootShell()
val zygiskPath = "/data/adb/modules/zygisksu"
val rezygiskPath = "/data/adb/modules/rezygisk"
val result = when {
SuFile(zygiskPath, "module.prop").exists() && !SuFile(zygiskPath, "disable").exists() ->
ShellUtils.fastCmd("grep '^name=' $zygiskPath/module.prop | cut -d'=' -f2")
SuFile(rezygiskPath, "module.prop").exists() && !SuFile(rezygiskPath, "disable").exists() ->
ShellUtils.fastCmd("grep '^name=' $rezygiskPath/module.prop | cut -d'=' -f2")
else -> "None"
}.trim()
val result = if (ShellUtils.fastCmdResult(shell, "test -f $zygiskPath/module.prop && test ! -f $zygiskPath/disable")) {
ShellUtils.fastCmd(shell, "grep '^name=' $zygiskPath/module.prop | cut -d'=' -f2")
} else if (ShellUtils.fastCmdResult(shell, "test -f $rezygiskPath/module.prop && test ! -f $rezygiskPath/disable")) {
ShellUtils.fastCmd(shell, "grep '^name=' $rezygiskPath/module.prop | cut -d'=' -f2")
} else {
"None"
}
Log.i(TAG, "Zygisk implement: $result")
return result
}

View File

@@ -3,10 +3,9 @@ package com.sukisu.ultra.ui.util
import android.content.Context
import android.os.Build
import android.system.Os
import com.topjohnwu.superuser.ShellUtils
import com.sukisu.ultra.Natives
import com.sukisu.ultra.ui.screen.getManagerVersion
import com.topjohnwu.superuser.Shell
import com.topjohnwu.superuser.ShellUtils
import java.io.File
import java.io.FileWriter
import java.io.PrintWriter
@@ -39,28 +38,30 @@ fun getBugreportFile(context: Context): File {
val bootConfig = File(bugreportDir, "boot_config.txt")
val kernelConfig = File(bugreportDir, "defconfig.gz")
Shell.cmd("dmesg > ${dmesgFile.absolutePath}").exec()
Shell.cmd("logcat -d > ${logcatFile.absolutePath}").exec()
Shell.cmd("tar -czf ${tombstonesFile.absolutePath} -C /data/tombstones .").exec()
Shell.cmd("tar -czf ${dropboxFile.absolutePath} -C /data/system/dropbox .").exec()
Shell.cmd("tar -czf ${pstoreFile.absolutePath} -C /sys/fs/pstore .").exec()
Shell.cmd("tar -czf ${diagFile.absolutePath} -C /data/vendor/diag . --exclude=./minidump.gz").exec()
Shell.cmd("tar -czf ${oplusFile.absolutePath} -C /mnt/oplus/op2/media/log/boot_log/ .").exec()
Shell.cmd("tar -czf ${bootlogFile.absolutePath} -C /data/adb/ksu/log .").exec()
val shell = getRootShell(true)
Shell.cmd("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").exec()
Shell.cmd("cat /proc/filesystems > ${fileSystemsFile.absolutePath}").exec()
Shell.cmd("busybox tree /data/adb > ${adbFileTree.absolutePath}").exec()
Shell.cmd("ls -alRZ /data/adb > ${adbFileDetails.absolutePath}").exec()
Shell.cmd("du -sh /data/adb/ksu/* > ${ksuFileSize.absolutePath}").exec()
Shell.cmd("cp /data/system/packages.list ${appListFile.absolutePath}").exec()
Shell.cmd("getprop > ${propFile.absolutePath}").exec()
Shell.cmd("cp /data/adb/ksu/.allowlist ${allowListFile.absolutePath}").exec()
Shell.cmd("cp /proc/modules ${procModules.absolutePath}").exec()
Shell.cmd("cp /proc/bootconfig ${bootConfig.absolutePath}").exec()
Shell.cmd("cp /proc/config.gz ${kernelConfig.absolutePath}").exec()
shell.newJob().add("dmesg > ${dmesgFile.absolutePath}").exec()
shell.newJob().add("logcat -d > ${logcatFile.absolutePath}").exec()
shell.newJob().add("tar -czf ${tombstonesFile.absolutePath} -C /data/tombstones .").exec()
shell.newJob().add("tar -czf ${dropboxFile.absolutePath} -C /data/system/dropbox .").exec()
shell.newJob().add("tar -czf ${pstoreFile.absolutePath} -C /sys/fs/pstore .").exec()
shell.newJob().add("tar -czf ${diagFile.absolutePath} -C /data/vendor/diag . --exclude=./minidump.gz").exec()
shell.newJob().add("tar -czf ${oplusFile.absolutePath} -C /mnt/oplus/op2/media/log/boot_log/ .").exec()
shell.newJob().add("tar -czf ${bootlogFile.absolutePath} -C /data/adb/ksu/log .").exec()
val selinux = ShellUtils.fastCmd("getenforce")
shell.newJob().add("cat /proc/1/mountinfo > ${mountsFile.absolutePath}").exec()
shell.newJob().add("cat /proc/filesystems > ${fileSystemsFile.absolutePath}").exec()
shell.newJob().add("busybox tree /data/adb > ${adbFileTree.absolutePath}").exec()
shell.newJob().add("ls -alRZ /data/adb > ${adbFileDetails.absolutePath}").exec()
shell.newJob().add("du -sh /data/adb/ksu/* > ${ksuFileSize.absolutePath}").exec()
shell.newJob().add("cp /data/system/packages.list ${appListFile.absolutePath}").exec()
shell.newJob().add("getprop > ${propFile.absolutePath}").exec()
shell.newJob().add("cp /data/adb/ksu/.allowlist ${allowListFile.absolutePath}").exec()
shell.newJob().add("cp /proc/modules ${procModules.absolutePath}").exec()
shell.newJob().add("cp /proc/bootconfig ${bootConfig.absolutePath}").exec()
shell.newJob().add("cp /proc/config.gz ${kernelConfig.absolutePath}").exec()
val selinux = ShellUtils.fastCmd(shell, "getenforce")
// basic information
val buildInfo = File(bugreportDir, "basic.txt")
@@ -101,9 +102,9 @@ fun getBugreportFile(context: Context): File {
val targetFile = File(context.cacheDir, "KernelSU_bugreport_${current}.tar.gz")
Shell.cmd("tar czf ${targetFile.absolutePath} -C ${bugreportDir.absolutePath} .").exec()
Shell.cmd("rm -rf ${bugreportDir.absolutePath}").exec()
Shell.cmd("chmod 0644 ${targetFile.absolutePath}").exec()
shell.newJob().add("tar czf ${targetFile.absolutePath} -C ${bugreportDir.absolutePath} .").exec()
shell.newJob().add("rm -rf ${bugreportDir.absolutePath}").exec()
shell.newJob().add("chmod 0644 ${targetFile.absolutePath}").exec()
return targetFile
}

View File

@@ -4,7 +4,6 @@ import android.content.Context
import android.net.Uri
import android.util.Log
import com.sukisu.ultra.Natives
import com.topjohnwu.superuser.Shell
import java.io.File
import java.io.FileOutputStream
@@ -125,17 +124,18 @@ object ModuleVerificationManager {
// 为指定模块创建验证标志文件
fun createVerificationFlag(moduleId: String): Boolean {
return try {
val shell = getRootShell()
val flagFilePath = "$VERIFICATION_FLAGS_DIR/$moduleId"
// 确保目录存在
val createDirCommand = "mkdir -p '$VERIFICATION_FLAGS_DIR'"
Shell.cmd(createDirCommand).exec()
shell.newJob().add(createDirCommand).exec()
// 创建验证标志文件,写入验证时间戳
val timestamp = System.currentTimeMillis()
val command = "echo '$timestamp' > '$flagFilePath'"
val result = Shell.cmd(command).exec()
val result = shell.newJob().add(command).exec()
if (result.isSuccess) {
Log.d(TAG, "验证标志文件创建成功: $flagFilePath")
@@ -152,10 +152,11 @@ object ModuleVerificationManager {
fun removeVerificationFlag(moduleId: String): Boolean {
return try {
val shell = getRootShell()
val flagFilePath = "$VERIFICATION_FLAGS_DIR/$moduleId"
val command = "rm -f '$flagFilePath'"
val result = Shell.cmd(command).exec()
val result = shell.newJob().add(command).exec()
if (result.isSuccess) {
Log.d(TAG, "验证标志文件移除成功: $flagFilePath")
@@ -172,10 +173,11 @@ object ModuleVerificationManager {
fun getVerificationTimestamp(moduleId: String): Long {
return try {
val shell = getRootShell()
val flagFilePath = "$VERIFICATION_FLAGS_DIR/$moduleId"
val command = "cat '$flagFilePath' 2>/dev/null || echo '0'"
val result = Shell.cmd(command).to(ArrayList(), null).exec()
val result = shell.newJob().add(command).to(ArrayList(), null).exec()
if (result.isSuccess && result.out.isNotEmpty()) {
val timestampStr = result.out.firstOrNull()?.trim() ?: "0"
@@ -193,11 +195,12 @@ object ModuleVerificationManager {
if (moduleIds.isEmpty()) return emptyMap()
return try {
val shell = getRootShell()
val result = mutableMapOf<String, Boolean>()
// 确保目录存在
val createDirCommand = "mkdir -p '$VERIFICATION_FLAGS_DIR'"
Shell.cmd(createDirCommand).exec()
shell.newJob().add(createDirCommand).exec()
// 批量检查所有模块的验证标志文件
val commands = moduleIds.map { moduleId ->
@@ -205,7 +208,7 @@ object ModuleVerificationManager {
}
val command = commands.joinToString(" && ")
val shellResult = Shell.cmd(command).to(ArrayList(), null).exec()
val shellResult = shell.newJob().add(command).to(ArrayList(), null).exec()
if (shellResult.isSuccess) {
shellResult.out.forEach { line ->

View File

@@ -7,17 +7,22 @@ import android.content.pm.ApplicationInfo
import android.content.pm.PackageInfo
import android.util.Log
import android.widget.Toast
import androidx.core.content.edit
import com.dergoogler.mmrl.platform.Platform.Companion.context
import com.sukisu.ultra.Natives
import com.sukisu.ultra.R
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.*
import org.json.JSONObject
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileOutputStream
import java.io.IOException
import androidx.core.content.edit
import com.sukisu.ultra.ui.viewmodel.SuperUserViewModel
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import org.json.JSONObject
import java.text.SimpleDateFormat
import java.util.*
@@ -425,10 +430,12 @@ object SuSFSManager {
async(Dispatchers.IO) {
val dataPath = "$MEDIA_DATA_PATH/${appInfo.packageName}"
val exists = try {
val shell = getRootShell()
val outputList = mutableListOf<String>()
val errorList = mutableListOf<String>()
val result = Shell.cmd("[ -d \"$dataPath\" ] && echo 'exists' || echo 'not_exists'")
val result = shell.newJob()
.add("[ -d \"$dataPath\" ] && echo 'exists' || echo 'not_exists'")
.to(outputList, errorList)
.exec()

View File

@@ -7,26 +7,26 @@ import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.core.content.edit
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.dergoogler.mmrl.platform.model.ModuleConfig
import com.dergoogler.mmrl.platform.model.ModuleConfig.Companion.asModuleConfig
import com.sukisu.ultra.ui.util.HanziToPinyin
import com.sukisu.ultra.ui.util.ModuleVerificationManager
import com.sukisu.ultra.ui.util.listModules
import com.topjohnwu.superuser.Shell
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import com.sukisu.ultra.ui.util.HanziToPinyin
import com.sukisu.ultra.ui.util.listModules
import com.sukisu.ultra.ui.util.getRootShell
import com.sukisu.ultra.ui.util.ModuleVerificationManager
import kotlinx.coroutines.withContext
import org.json.JSONArray
import org.json.JSONObject
import java.text.Collator
import java.text.DecimalFormat
import java.util.*
import java.util.Locale
import java.util.concurrent.TimeUnit
import kotlin.math.log10
import kotlin.math.pow
import androidx.core.content.edit
/**
* @author ShirkNeko
@@ -452,8 +452,9 @@ class ModuleSizeCache(context: Context) {
*/
private fun calculateModuleFolderSize(dirId: String): Long {
return try {
val shell = getRootShell()
val command = "du -sb /data/adb/modules/$dirId"
val result = Shell.cmd(command).to(ArrayList(), null).exec()
val result = shell.newJob().add(command).to(ArrayList(), null).exec()
if (result.isSuccess && result.out.isNotEmpty()) {
val sizeStr = result.out.firstOrNull()?.split("\t")?.firstOrNull()

View File

@@ -1,13 +1,15 @@
package io.sukisu.ultra;
import com.topjohnwu.superuser.Shell;
import java.util.ArrayList;
import com.sukisu.ultra.ui.util.KsuCli;
public class UltraShellHelper {
public static String runCmd(String cmds) {
StringBuilder sb = new StringBuilder();
for(String str : Shell.cmd(cmds)
for(String str : KsuCli.INSTANCE.getGLOBAL_MNT_SHELL()
.newJob()
.add(cmds)
.to(new ArrayList<>(), null)
.exec()
.getOut()) {
@@ -16,6 +18,11 @@ public class UltraShellHelper {
return sb.toString();
}
public static boolean isPathExists(String path) {
String result = runCmd("test -f '" + path + "' && echo 'exists'");
return result.contains("exists");
}
public static void CopyFileTo(String path, String target) {
runCmd("cp -f '" + path + "' '" + target + "' 2>&1");
}

View File

@@ -1,7 +1,5 @@
package io.sukisu.ultra;
import com.topjohnwu.superuser.io.SuFile;
import static com.sukisu.ultra.ui.util.KsuCliKt.getKpmmgrPath;
import static com.sukisu.ultra.ui.util.KsuCliKt.getSuSFSDaemonPath;
@@ -13,17 +11,15 @@ public class UltraToolInstall {
@SuppressLint("SetWorldReadable")
@SuppressWarnings("ResultOfMethodCallIgnored")
public static void tryToInstall() {
SuFile KpmmgrFile = new SuFile(OUTSIDE_KPMMGR_PATH);
if (KpmmgrFile.exists()) {
UltraShellHelper.CopyFileTo(getKpmmgrPath(), OUTSIDE_KPMMGR_PATH);
KpmmgrFile.setReadable(true, false);
KpmmgrFile.setExecutable(true, false);
String kpmmgrPath = getKpmmgrPath();
if (UltraShellHelper.isPathExists(OUTSIDE_KPMMGR_PATH)) {
UltraShellHelper.CopyFileTo(kpmmgrPath, OUTSIDE_KPMMGR_PATH);
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_KPMMGR_PATH);
}
SuFile SuSFSDaemonFile = new SuFile(OUTSIDE_SUSFSD_PATH);
if (SuSFSDaemonFile.exists()) {
UltraShellHelper.CopyFileTo(getSuSFSDaemonPath(), OUTSIDE_SUSFSD_PATH);
SuSFSDaemonFile.setReadable(true, false);
SuSFSDaemonFile.setExecutable(true, false);
String SuSFSDaemonPath = getSuSFSDaemonPath();
if (UltraShellHelper.isPathExists(OUTSIDE_SUSFSD_PATH)) {
UltraShellHelper.CopyFileTo(SuSFSDaemonPath, OUTSIDE_SUSFSD_PATH);
UltraShellHelper.runCmd("chmod a+rx " + OUTSIDE_SUSFSD_PATH);
}
}
}