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 dalvik cache and package manager cache entry on every boot.

Fixes: #275, #303, #307

Signed-off-by: Andrew Gunnerson <[email protected]>
  • Loading branch information
chenxiaolong committed Apr 22, 2023
1 parent 2c75213 commit 6357df4
Show file tree
Hide file tree
Showing 6 changed files with 174 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)"
8 changes: 8 additions & 0 deletions app/magisk/post-fs-data.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# 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 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,127 @@
@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.name
import kotlin.io.path.readBytes
import kotlin.io.path.walk
import kotlin.system.exitProcess

private val DALVIK_CACHE_DIR = Paths.get("/data/dalvik-cache")
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 clearDalvikCache(appId: String): Boolean {
var ret = true

for (path in DALVIK_CACHE_DIR.walk()) {
if (!path.isRegularFile()) {
continue
}

val pieces = path.name.split('@')
val appIdIndex = pieces.indexOf(appId)
if (appIdIndex < 0 || appIdIndex == pieces.size - 1) {
continue
}

val nextPiece = pieces[appIdIndex + 1]
if (nextPiece.startsWith("app-") && nextPiece.endsWith(".apk")) {
try {
delete(path)
} catch (e: Exception) {
e.printStackTrace()
ret = false
}
}
}

return ret
}

@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() {
clearDalvikCache(BuildConfig.APPLICATION_ID)
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 6357df4

Please sign in to comment.