diff --git a/app/build.gradle.kts b/app/build.gradle.kts index ad6937558..d7ced0647 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -350,6 +350,8 @@ android.applicationVariants.all { } } + from(File(magiskDir, "boot_common.sh")) + from(File(magiskDir, "post-fs-data.sh")) from(File(magiskDir, "service.sh")) from(File(rootDir, "LICENSE")) diff --git a/app/magisk/boot_common.sh b/app/magisk/boot_common.sh new file mode 100644 index 000000000..654986427 --- /dev/null +++ b/app/magisk/boot_common.sh @@ -0,0 +1,34 @@ +# source "${0%/*}/boot_common.sh" + +exec >"${1}" 2>&1 + +mod_dir=${0%/*} + +header() { + echo "----- ${*} -----" +} + +module_prop() { + grep "^${1}=" "${mod_dir}/module.prop" | cut -d= -f2 +} + +run_cli_apk() { + CLASSPATH="${cli_apk}" app_process / "${@}" & + pid=${!} + wait "${pid}" + echo "Exit status: ${?}" + echo "Logcat:" + logcat -d --pid "${pid}" +} + +app_id=$(module_prop id) +app_version=$(module_prop version) +cli_apk=$(echo "${mod_dir}"/system/priv-app/"${app_id}"/app-*.apk) + +header Environment +echo "Timestamp: $(date)" +echo "Script: ${0}" +echo "App ID: ${app_id}" +echo "App version: ${app_version}" +echo "CLI APK: ${cli_apk}" +echo "UID/GID/Context: $(id)" diff --git a/app/magisk/post-fs-data.sh b/app/magisk/post-fs-data.sh new file mode 100644 index 000000000..7669f36b8 --- /dev/null +++ b/app/magisk/post-fs-data.sh @@ -0,0 +1,13 @@ +# On some devices, upgrading BCR seems to cause some old state to unexpectedly +# linger around, causing BCR to crash with an obscure error about the theme not +# being derived from Theme.AppCompat. + +source "${0%/*}/boot_common.sh" /data/local/tmp/bcr_clear_package_manager_caches.log + +header Timestamps +ls -lZ "${cli_apk}" +ls -lZ "${cli_apk#"${mod_dir}"}" +find /data/system/package_cache -name "${app_id}-*" -exec ls -lZ {} \+ + +header Clear package manager caches +run_cli_apk com.chiller3.bcr.standalone.ClearPackageManagerCachesKt diff --git a/app/magisk/service.sh b/app/magisk/service.sh index d30387870..b6ea49200 100644 --- a/app/magisk/service.sh +++ b/app/magisk/service.sh @@ -5,38 +5,10 @@ # to alter the flags. This command blocks for an arbitrary amount of time # because it needs to wait until the primary user unlocks the device. -exec >/data/local/tmp/bcr_remove_hard_restrictions.log 2>&1 - -mod_dir=${0%/*} - -header() { - echo "----- ${*} -----" -} - -module_prop() { - grep "^${1}=" "${mod_dir}/module.prop" | cut -d= -f2 -} - -app_id=$(module_prop id) -app_version=$(module_prop version) - -header Environment -echo "Timestamp: $(date)" -echo "Args: ${0} ${*}" -echo "Version: ${app_version}" -echo "UID/GID/Context: $(id)" +source "${0%/*}/boot_common.sh" /data/local/tmp/bcr_remove_hard_restrictions.log header Remove hard restrictions -CLASSPATH=$(find "${mod_dir}"/system/priv-app/"${app_id}" -name '*.apk') \ - app_process \ - / \ - com.chiller3.bcr.standalone.RemoveHardRestrictionsKt & -pid=${!} -wait "${pid}" -echo "Exit status: ${?}" - -header Logcat -logcat -d --pid "${pid}" +run_cli_apk com.chiller3.bcr.standalone.RemoveHardRestrictionsKt header Package state dumpsys package "${app_id}" diff --git a/app/src/main/java/com/chiller3/bcr/standalone/ClearPackageManagerCaches.kt b/app/src/main/java/com/chiller3/bcr/standalone/ClearPackageManagerCaches.kt new file mode 100644 index 000000000..672d1a973 --- /dev/null +++ b/app/src/main/java/com/chiller3/bcr/standalone/ClearPackageManagerCaches.kt @@ -0,0 +1,95 @@ +@file:Suppress("SameParameterValue") + +package com.chiller3.bcr.standalone + +import com.chiller3.bcr.BuildConfig +import java.nio.file.Path +import java.nio.file.Paths +import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.deleteIfExists +import kotlin.io.path.isRegularFile +import kotlin.io.path.readBytes +import kotlin.io.path.walk +import kotlin.system.exitProcess + +private val PACKAGE_CACHE_DIR = Paths.get("/data/system/package_cache") + +private var dryRun = false + +private fun delete(path: Path) { + if (dryRun) { + println("Would have deleted: $path") + } else { + println("Deleting: $path") + path.deleteIfExists() + } +} + +private fun ByteArray.indexOfSubarray(needle: ByteArray, start: Int = 0): Int { + require(start >= 0) { "start must be non-negative" } + + if (needle.isEmpty()) { + return 0 + } + + outer@ for (i in 0 until size - needle.size + 1) { + for (j in needle.indices) { + if (this[i + j] != needle[j]) { + continue@outer + } + } + return i + } + + return -1 +} + +@OptIn(ExperimentalPathApi::class) +private fun clearPackageManagerCache(appId: String): Boolean { + // The current implementation of the package cache uses PackageImpl.writeToParcel(), which + // serializes the cache entry to the file as a Parcel. The current Parcel implementation stores + // string values as null-terminated little-endian UTF-16. One of the string values stored is + // manifestPackageName, which we can match on. + // + // This is a unique enough search that there should never be a false positive, but even if there + // is, the package manager will just repopulate the cache. + val needle = "\u0000$appId\u0000".toByteArray(Charsets.UTF_16LE) + var ret = true + + for (path in PACKAGE_CACHE_DIR.walk()) { + if (!path.isRegularFile()) { + continue + } + + try { + // Not the most efficient, but these are tiny files that Android is later going to read + // entirely into memory anyway + if (path.readBytes().indexOfSubarray(needle) >= 0) { + delete(path) + } + } catch (e: Exception) { + e.printStackTrace() + ret = false + } + } + + return ret +} + +private fun mainInternal() { + clearPackageManagerCache(BuildConfig.APPLICATION_ID) +} + +fun main(args: Array) { + if ("--dry-run" in args) { + dryRun = true + } + + try { + mainInternal() + } catch (e: Exception) { + System.err.println("Failed to clear caches") + e.printStackTrace() + exitProcess(1) + } +} \ No newline at end of file diff --git a/app/src/main/java/com/chiller3/bcr/standalone/RemoveHardRestrictions.kt b/app/src/main/java/com/chiller3/bcr/standalone/RemoveHardRestrictions.kt index 05102befc..c8caa4503 100644 --- a/app/src/main/java/com/chiller3/bcr/standalone/RemoveHardRestrictions.kt +++ b/app/src/main/java/com/chiller3/bcr/standalone/RemoveHardRestrictions.kt @@ -272,7 +272,7 @@ private fun waitForLogin(userId: Int) { "User $userId did not unlock the device after $IS_USER_UNLOCKED_ATTEMPTS attempts") } -fun mainInternal() { +private fun mainInternal() { if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) { println("Android 9 does not have hard-restricted permissions") return