diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/Const.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/Const.kt index 2b5948511..92cf629e5 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/Const.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/Const.kt @@ -14,7 +14,7 @@ object Const { else Build.SUPPORTED_32_BIT_ABIS.firstOrNull() // Paths - const val MAGISK_PATH = "/data/adb/modules" + const val MODULE_PATH = "/data/adb/modules" const val TMPDIR = "/dev/tmp" const val MAGISK_LOG = "/cache/magisk.log" diff --git a/app/core/src/main/java/com/topjohnwu/magisk/core/model/module/LocalModule.kt b/app/core/src/main/java/com/topjohnwu/magisk/core/model/module/LocalModule.kt index 2bbb962ee..69a19ef1a 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/core/model/module/LocalModule.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/core/model/module/LocalModule.kt @@ -5,6 +5,7 @@ import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.di.ServiceLocator import com.topjohnwu.magisk.core.utils.RootUtils import com.topjohnwu.superuser.Shell +import com.topjohnwu.superuser.nio.ExtendedFile import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import timber.log.Timber @@ -12,7 +13,7 @@ import java.io.IOException import java.util.Locale data class LocalModule( - private val path: String, + private val base: ExtendedFile, ) : Module() { private val svc get() = ServiceLocator.networkService @@ -24,20 +25,18 @@ data class LocalModule( var description: String = "" var updateInfo: OnlineModule? = null var outdated = false - private var updateUrl: String = "" - private val removeFile = RootUtils.fs.getFile(path, "remove") - private val disableFile = RootUtils.fs.getFile(path, "disable") - private val updateFile = RootUtils.fs.getFile(path, "update") - private val riruFolder = RootUtils.fs.getFile(path, "riru") - private val zygiskFolder = RootUtils.fs.getFile(path, "zygisk") - private val unloaded = RootUtils.fs.getFile(zygiskFolder, "unloaded") - val updated: Boolean get() = updateFile.exists() - val isRiru: Boolean get() = (id == "riru-core") || riruFolder.exists() - val isZygisk: Boolean get() = zygiskFolder.exists() - val zygiskUnloaded: Boolean get() = unloaded.exists() - val hasAction: Boolean; + private val removeFile = base.getChildFile("remove") + private val disableFile = base.getChildFile("disable") + private val updateFile = base.getChildFile("update") + val zygiskFolder = base.getChildFile("zygisk") + + val updated get() = updateFile.exists() + val isRiru = (id == "riru-core") || base.getChildFile("riru").exists() + val isZygisk = zygiskFolder.exists() + val zygiskUnloaded = zygiskFolder.getChildFile("unloaded").exists() + val hasAction = base.getChildFile("action.sh").exists() var enable: Boolean get() = !disableFile.exists() @@ -90,19 +89,16 @@ data class LocalModule( init { runCatching { - parseProps(Shell.cmd("dos2unix < $path/module.prop").exec().out) + parseProps(Shell.cmd("dos2unix < $base/module.prop").exec().out) } if (id.isEmpty()) { - val sep = path.lastIndexOf('/') - id = path.substring(sep + 1) + id = base.name } if (name.isEmpty()) { name = id } - - hasAction = RootUtils.fs.getFile(path, "action.sh").exists() } suspend fun fetch(): Boolean { @@ -125,14 +121,14 @@ data class LocalModule( companion object { - fun loaded() = RootUtils.fs.getFile(Const.MAGISK_PATH).exists() + fun loaded() = RootUtils.fs.getFile(Const.MODULE_PATH).exists() suspend fun installed() = withContext(Dispatchers.IO) { - RootUtils.fs.getFile(Const.MAGISK_PATH) + RootUtils.fs.getFile(Const.MODULE_PATH) .listFiles() .orEmpty() .filter { !it.isFile && !it.isHidden } - .map { LocalModule("${Const.MAGISK_PATH}/${it.name}") } + .map { LocalModule(it) } .sortedBy { it.name.lowercase(Locale.ROOT) } } } diff --git a/app/core/src/main/java/com/topjohnwu/magisk/test/AdditionalTest.kt b/app/core/src/main/java/com/topjohnwu/magisk/test/AdditionalTest.kt index dcba012f5..7cf47581b 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/test/AdditionalTest.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/test/AdditionalTest.kt @@ -5,9 +5,17 @@ import androidx.annotation.Keep import androidx.test.ext.junit.runners.AndroidJUnit4 import androidx.test.uiautomator.By import androidx.test.uiautomator.Until +import com.topjohnwu.magisk.core.model.module.LocalModule +import com.topjohnwu.magisk.core.utils.RootUtils +import kotlinx.coroutines.runBlocking import org.junit.After +import org.junit.Assert.assertEquals +import org.junit.Assert.assertFalse import org.junit.Assert.assertNotNull +import org.junit.Assert.assertNull +import org.junit.Assert.assertTrue import org.junit.Assume.assumeTrue +import org.junit.BeforeClass import org.junit.Test import org.junit.runner.RunWith import java.util.concurrent.TimeUnit @@ -21,6 +29,17 @@ class AdditionalTest : BaseTest { private const val SHELL_PKG = "com.android.shell" private const val LSPOSED_CATEGORY = "org.lsposed.manager.LAUNCH_MANAGER" private const val LSPOSED_PKG = "org.lsposed.manager" + + private lateinit var modules: List + + @BeforeClass + @JvmStatic + fun before() { + BaseTest.prerequisite() + runBlocking { + modules = LocalModule.installed() + } + } } @After @@ -29,9 +48,23 @@ class AdditionalTest : BaseTest { } @Test - fun testLaunchLsposedManager() { + fun testModuleCount() { + var expected = 2 + if (Environment.lsposed()) expected++ + if (Environment.shamiko()) expected++ + assertEquals("Module count incorrect", expected, modules.size) + } + + @Test + fun testLsposed() { assumeTrue(Environment.lsposed()) + val module = modules.find { it.id == "zygisk_lsposed" } + assertNotNull("zygisk_lsposed is not installed", module) + module!! + assertFalse("zygisk_lsposed is not enabled", module.zygiskUnloaded) + + // Launch lsposed manager to ensure the module is active uiAutomation.executeShellCommand( "am start -c $LSPOSED_CATEGORY $SHELL_PKG/.BugreportWarningActivity" ).let { pfd -> AutoCloseInputStream(pfd).use { it.readBytes() } } @@ -42,4 +75,33 @@ class AdditionalTest : BaseTest { device.wait(Until.hasObject(By.res(pattern)), TimeUnit.SECONDS.toMillis(10)) ) } + + @Test + fun testModule01() { + val module = modules.find { it.id == "test_01" } + assertNotNull("test_01 is not installed", module) + assertTrue( + "/system/etc/newfile should exist", + RootUtils.fs.getFile("/system/etc/newfile").exists() + ) + assertFalse( + "/system/bin/screenrecord should not exist", + RootUtils.fs.getFile("/system/bin/screenrecord").exists() + ) + module!! + assertTrue("test_01 should be zygisk unloaded", module.zygiskUnloaded) + } + + @Test + fun testModule02() { + val module = modules.find { it.id == "test_02" } + assertNotNull("test_02 is not installed", module) + module!! + assertTrue("test_02 should be zygisk unloaded", module.zygiskUnloaded) + } + + @Test + fun testModule03() { + assertNull("test_03 should be removed", modules.find { it.id == "test_03" }) + } } diff --git a/app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt b/app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt index bb67d97a3..a4147fec3 100644 --- a/app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt +++ b/app/core/src/main/java/com/topjohnwu/magisk/test/Environment.kt @@ -6,13 +6,18 @@ import androidx.annotation.Keep import androidx.core.net.toUri import androidx.test.ext.junit.runners.AndroidJUnit4 import com.topjohnwu.magisk.core.BuildConfig.APP_PACKAGE_NAME +import com.topjohnwu.magisk.core.Const import com.topjohnwu.magisk.core.download.DownloadNotifier import com.topjohnwu.magisk.core.download.DownloadProcessor import com.topjohnwu.magisk.core.ktx.cachedFile +import com.topjohnwu.magisk.core.model.module.LocalModule import com.topjohnwu.magisk.core.tasks.AppMigration import com.topjohnwu.magisk.core.tasks.FlashZip import com.topjohnwu.magisk.core.tasks.MagiskInstaller +import com.topjohnwu.magisk.core.utils.RootUtils import com.topjohnwu.superuser.CallbackList +import com.topjohnwu.superuser.Shell +import com.topjohnwu.superuser.nio.ExtendedFile import kotlinx.coroutines.runBlocking import org.apache.commons.compress.archivers.zip.ZipFile import org.junit.Assert.assertArrayEquals @@ -37,7 +42,7 @@ class Environment : BaseTest { return Build.VERSION.SDK_INT >= 27 && Build.VERSION.SDK_INT <= 34 } - private fun shamiko(): Boolean { + fun shamiko(): Boolean { return Build.VERSION.SDK_INT >= 27 } @@ -72,6 +77,55 @@ class Environment : BaseTest { } } + private fun setupModule01(root: ExtendedFile) { + val error = "test_01 setup failed" + val path = root.getChildFile("test_01") + + // Create /system/etc/newfile + val etc = path.getChildFile("system").getChildFile("etc") + assertTrue(error, etc.mkdirs()) + assertTrue(error, etc.getChildFile("newfile").createNewFile()) + + // Delete /system/bin/screenrecord + val bin = path.getChildFile("system").getChildFile("bin") + assertTrue(error, bin.mkdirs()) + assertTrue(error, Shell.cmd("mknod $bin/screenrecord c 0 0").exec().isSuccess) + + // Create an empty zygisk folder + val module = LocalModule(path) + assertTrue(error, module.zygiskFolder.mkdir()) + + assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess) + } + + private fun setupModule02(root: ExtendedFile) { + val error = "test_02 setup failed" + val path = root.getChildFile("test_02") + + // Create invalid zygisk libraries + val module = LocalModule(path) + assertTrue(error, module.zygiskFolder.mkdirs()) + assertTrue(error, module.zygiskFolder.getChildFile("armeabi-v7a.so").createNewFile()) + assertTrue(error, module.zygiskFolder.getChildFile("arm64-v8a.so").createNewFile()) + assertTrue(error, module.zygiskFolder.getChildFile("x86.so").createNewFile()) + assertTrue(error, module.zygiskFolder.getChildFile("x86_64.so").createNewFile()) + + assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess) + } + + private fun setupModule03(root: ExtendedFile) { + val error = "test_03 setup failed" + val path = root.getChildFile("test_03") + + // Create a new module but mark is as "remove" + val module = LocalModule(path) + assertTrue(error, path.mkdirs()) + assertTrue(error, path.getChildFile("service.sh").createNewFile()) + module.remove = true + + assertTrue(error, Shell.cmd("set_default_perm $path").exec().isSuccess) + } + @Test fun setupEnvironment() { runBlocking { @@ -114,6 +168,11 @@ class Environment : BaseTest { ) } } + + val root = RootUtils.fs.getFile(Const.MODULE_PATH) + setupModule01(root) + setupModule02(root) + setupModule03(root) } @Test diff --git a/scripts/util_functions.sh b/scripts/util_functions.sh index a1923ec49..77bd58c3f 100644 --- a/scripts/util_functions.sh +++ b/scripts/util_functions.sh @@ -621,6 +621,15 @@ is_legacy_script() { return $? } +# $1 = MODPATH +set_default_perm() { + set_perm_recursive $1 0 0 0755 0644 + set_perm_recursive $1/system/bin 0 2000 0755 0755 + set_perm_recursive $1/system/xbin 0 2000 0755 0755 + set_perm_recursive $1/system/system_ext/bin 0 2000 0755 0755 + set_perm_recursive $1/system/vendor/bin 0 2000 0755 0755 u:object_r:vendor_file:s0 +} + # Require OUTFD, ZIPFILE to be set install_module() { rm -rf $TMPDIR @@ -683,13 +692,7 @@ install_module() { if ! grep -q '^SKIPUNZIP=1$' $MODPATH/customize.sh 2>/dev/null; then ui_print "- Extracting module files" unzip -o "$ZIPFILE" -x 'META-INF/*' -d $MODPATH >&2 - - # Default permissions - set_perm_recursive $MODPATH 0 0 0755 0644 - set_perm_recursive $MODPATH/system/bin 0 2000 0755 0755 - set_perm_recursive $MODPATH/system/xbin 0 2000 0755 0755 - set_perm_recursive $MODPATH/system/system_ext/bin 0 2000 0755 0755 - set_perm_recursive $MODPATH/system/vendor/bin 0 2000 0755 0755 u:object_r:vendor_file:s0 + set_default_perm $MODPATH fi # Load customization script