Skip to content

Commit

Permalink
Clean package manager caches on boot
Browse files Browse the repository at this point in the history
Some devices seem to have an issue where (presumably) resources from an
old version are being used with new code. This causes BCR to crash with
an error about the app theme not being derived from Theme.AppCompat.
This commit works around the issue in a brute force way by deleting
BCR's package manager cache entry on every boot.

Fixes: #275, #303, #307

Signed-off-by: Andrew Gunnerson <[email protected]>
  • Loading branch information
chenxiaolong committed May 2, 2023
1 parent 5521ecc commit 897e5fb
Show file tree
Hide file tree
Showing 6 changed files with 147 additions and 31 deletions.
2 changes: 2 additions & 0 deletions app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
34 changes: 34 additions & 0 deletions app/magisk/boot_common.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# source "${0%/*}/boot_common.sh" <log file>

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)"
13 changes: 13 additions & 0 deletions app/magisk/post-fs-data.sh
Original file line number Diff line number Diff line change
@@ -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
32 changes: 2 additions & 30 deletions app/magisk/service.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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}"
Original file line number Diff line number Diff line change
@@ -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<String>) {
if ("--dry-run" in args) {
dryRun = true
}

try {
mainInternal()
} catch (e: Exception) {
System.err.println("Failed to clear caches")
e.printStackTrace()
exitProcess(1)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 897e5fb

Please sign in to comment.