diff --git a/manager/app/src/main/java/com/sukisu/ultra/KernelSUApplication.kt b/manager/app/src/main/java/com/sukisu/ultra/KernelSUApplication.kt index ea553ab0..73de13de 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/KernelSUApplication.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/KernelSUApplication.kt @@ -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) diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt index 6f343176..6f36c672 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/screen/Kpm.kt @@ -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 diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt index f12c06dd..16c90b68 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/KsuCli.kt @@ -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 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 = 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 { - 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(), 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 } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/LogEvent.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/LogEvent.kt index fd1885e0..758cc2b8 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/LogEvent.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/LogEvent.kt @@ -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 } diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt index 6f7ce6e6..9e70c40b 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/ModuleVerificationManager.kt @@ -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() // 确保目录存在 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 -> diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt index 40e6264e..0da7793e 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/util/SuSFSManager.kt @@ -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() val errorList = mutableListOf() - 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() diff --git a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt index fef0f154..f8dfe2e0 100644 --- a/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt +++ b/manager/app/src/main/java/com/sukisu/ultra/ui/viewmodel/ModuleViewModel.kt @@ -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() diff --git a/manager/app/src/main/java/io/sukisu/ultra/UltraShellHelper.java b/manager/app/src/main/java/io/sukisu/ultra/UltraShellHelper.java index 68b85228..f829f614 100644 --- a/manager/app/src/main/java/io/sukisu/ultra/UltraShellHelper.java +++ b/manager/app/src/main/java/io/sukisu/ultra/UltraShellHelper.java @@ -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"); } diff --git a/manager/app/src/main/java/io/sukisu/ultra/UltraToolInstall.java b/manager/app/src/main/java/io/sukisu/ultra/UltraToolInstall.java index 7d1bfdb4..209dbcb9 100644 --- a/manager/app/src/main/java/io/sukisu/ultra/UltraToolInstall.java +++ b/manager/app/src/main/java/io/sukisu/ultra/UltraToolInstall.java @@ -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); } } }