mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 21:27:02 +01:00
Extract mtk_root, with the libraries that lived outside it
github.com/kuhyx/mtk-root
Unlike the app extractions, mtk_root could not travel alone: its four
libraries and three of its four test suites sat in scripts/lib/, one
directory up from the stages that source them. Taking only the directory
would have orphaned eight files here and left the new repo unable to
source anything, so all twelve moved in one filter-repo pass. The
mtk_root/ + lib/ sibling layout is preserved, so every
`source "$SCRIPT_DIR/../lib/..."` resolves with no call site edited.
Two things the new repo needed that the monorepo provided implicitly:
lib/tests/run_all.sh -- the copy here is shared with the non-mtk suite
and stays. Without a new one, shell-tests.yml's
glob over */lib/tests/run_all.sh would have
stopped covering these suites on a green build.
.shellcheckrc -- external-sources + source-path=SCRIPTDIR, which
is what lets shellcheck run with no severity
filter and still pass on the ../lib/ sources.
mtk_harness.sh stays here despite the name: test_common_datetime.sh
sources it. Removing it broke that suite outright, which running the
suite caught immediately. Its header now describes what it actually
serves; the misleading name is kept on purpose, since renaming would
churn its one consumer for nothing.
Verified: 84 tests pass in the new repo with shellcheck clean, 55 still
pass here, and check_shell_coverage.sh --all exits 0 with the four
mtk_root allowlist entries gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PJrqKERAo5QUeEHWS5Asgp
This commit is contained in:
parent
a0459b99a0
commit
6b5fe8e7a7
@ -27,7 +27,8 @@ Extracted to their own repos: [`steam-backlog-enforcer`](https://github.com/kuhy
|
||||
[`build-your-x`](https://github.com/kuhyx/build-your-x) (lives at `~/build_your_x`),
|
||||
[`reverse-survivors`](https://github.com/kuhyx/reverse-survivors),
|
||||
[`kcd2-dice-solver`](https://github.com/kuhyx/kcd2-dice-solver) (lives at `~/kcd2-dice-solver`),
|
||||
[`focus-owner`](https://github.com/kuhyx/focus-owner).
|
||||
[`focus-owner`](https://github.com/kuhyx/focus-owner),
|
||||
[`mtk-root`](https://github.com/kuhyx/mtk-root).
|
||||
Archived work: [`testsAndMisc-archive`](https://github.com/kuhyx/testsAndMisc-archive).
|
||||
|
||||
## Adding a Flutter app
|
||||
|
||||
20
docs/superpowers/contracts/extract-mtk-root-2026-08-23.json
Normal file
20
docs/superpowers/contracts/extract-mtk-root-2026-08-23.json
Normal file
@ -0,0 +1,20 @@
|
||||
{
|
||||
"title": "Extract mtk_root together with the libraries that live outside it",
|
||||
"objective": "Move the MediaTek rooting toolkit to its own public repo. Unlike the three app extractions, mtk_root's libraries and half its test suites live in linux_configuration/scripts/lib/, one directory up. Extracting the directory alone would orphan eight files here and leave the new repo unable to source anything.",
|
||||
"acceptance_criteria": [
|
||||
"The new repo preserves the mtk_root/ + lib/ sibling layout, so every `source \"$SCRIPT_DIR/../lib/...\"` resolves without editing a single call site.",
|
||||
"All 84 mtk tests pass from a clean clone of the new repo.",
|
||||
"shellcheck exits 0 over every script in the new repo with no severity filter, which requires carrying .shellcheckrc across.",
|
||||
"The new repo has its own lib/tests/run_all.sh, because the monorepo's is shared with a non-mtk suite and must stay.",
|
||||
"The suite remaining in the monorepo still passes: removing a shared fixture must not take test_common_datetime.sh down with it.",
|
||||
"meta/shell-coverage-allowlist.txt has no entry naming a removed path.",
|
||||
"check_shell_coverage.sh --all exits 0.",
|
||||
"pre-commit passes without --no-verify."
|
||||
],
|
||||
"out_of_scope": [
|
||||
"single_use/utils/lib/bl9000_*.sh, which is separate Android rooting tooling that merely mentions mtkclient.",
|
||||
"Renaming mtk_harness.sh, whose name is now historical but whose one consumer would churn for no behavioural gain.",
|
||||
"Verifying the toolkit against real hardware, which READY.md already records as pending."
|
||||
],
|
||||
"verifier": "./lib/tests/run_all.sh in the new repo reports 84 passed / 0 failed; shellcheck over the new repo exits 0; ./linux_configuration/scripts/lib/tests/run_all.sh here reports 55 passed / 0 failed; bash meta/scripts/check_shell_coverage.sh --all exits 0; gh confirms the repo is public on main."
|
||||
}
|
||||
53
docs/superpowers/evidence/extract-mtk-root-2026-08-23.json
Normal file
53
docs/superpowers/evidence/extract-mtk-root-2026-08-23.json
Normal file
@ -0,0 +1,53 @@
|
||||
{
|
||||
"intent": "Extract the MediaTek rooting toolkit into its own public repo, taking the four mtk_* libraries and four test suites that lived outside mtk_root/ and would otherwise have been orphaned.",
|
||||
"scope": [
|
||||
"linux_configuration/scripts/mtk_root/ (24 files) -> github.com/kuhyx/mtk-root",
|
||||
"linux_configuration/scripts/lib/mtk_{classify,common,device,partitions}.sh - moved with it",
|
||||
"linux_configuration/scripts/lib/tests/test_mtk_{classify,device,partitions}.sh - moved with it",
|
||||
"lib/tests/mtk_harness.sh - deliberately KEPT, it is shared with test_common_datetime.sh",
|
||||
"meta/shell-coverage-allowlist.txt - four mtk_root entries removed",
|
||||
"Non-goal: single_use/utils/lib/bl9000_*.sh, separate Android tooling that merely mentions mtkclient"
|
||||
],
|
||||
"changes": [
|
||||
"Extracted mtk_root/ plus the eight external files in one filter-repo pass with --path-rename linux_configuration/scripts/:, preserving the mtk_root/ + lib/ sibling layout so every `source \"$SCRIPT_DIR/../lib/...\"` resolves unchanged.",
|
||||
"Added lib/tests/run_all.sh to the new repo. The monorepo's copy is shared with the non-mtk suite and correctly stayed behind; without a new one, shell-tests.yml's glob over */lib/tests/run_all.sh would have silently stopped covering these suites.",
|
||||
"Carried .shellcheckrc (external-sources, source-path=SCRIPTDIR) into the new repo, which is what lets its CI run shellcheck with no severity filter and still pass.",
|
||||
"Restored mtk_harness.sh after the initial removal broke test_common_datetime.sh, and rewrote its header to describe what it actually serves now. The misleading name is kept deliberately, since renaming would churn its one consumer.",
|
||||
"Removed the four mtk_root entries from meta/shell-coverage-allowlist.txt."
|
||||
],
|
||||
"verification": [
|
||||
{
|
||||
"command": "./lib/tests/run_all.sh (extracted repo)",
|
||||
"result": "pass",
|
||||
"evidence": "test_mtk_classify 22 passed, test_mtk_device 26 passed, test_mtk_partitions 36 passed - 84 total, 0 failed."
|
||||
},
|
||||
{
|
||||
"command": "find . -name '*.sh' | xargs shellcheck (extracted repo)",
|
||||
"result": "pass",
|
||||
"evidence": "Exit 0 after adding .shellcheckrc. Before it, the ../lib/ sources reported SC1091 info findings, which CI treats as failures."
|
||||
},
|
||||
{
|
||||
"command": "./linux_configuration/scripts/lib/tests/run_all.sh (monorepo, after removal)",
|
||||
"result": "fail then pass",
|
||||
"evidence": "First run: 'mtk_harness.sh: No such file or directory'. Removing the harness broke test_common_datetime.sh, which sources it. Restored; the suite reports 55 passed, 0 failed. This is the silent CI-coverage loss the plan warned about, caught by running the suite."
|
||||
},
|
||||
{
|
||||
"command": "bash meta/scripts/check_shell_coverage.sh --all",
|
||||
"result": "pass",
|
||||
"evidence": "Exit 0 with the four mtk_root allowlist entries removed."
|
||||
},
|
||||
{
|
||||
"command": "gh repo create + push",
|
||||
"result": "pass",
|
||||
"evidence": "github.com/kuhyx/mtk-root public on main, 6 commits of preserved history plus the standalone-setup commit."
|
||||
}
|
||||
],
|
||||
"risks": [
|
||||
"mtk_harness.sh keeps a name that no longer matches its contents; the header now says so explicitly rather than leaving a future reader to discover it.",
|
||||
"The toolkit targets hardware not currently in hand (per READY.md the Ulefone props are unverified), so the extracted repo's tests exercise decision logic only - the same limitation it had here."
|
||||
],
|
||||
"rollback": [
|
||||
"git revert the removal commit to restore mtk_root/ and the eight lib files.",
|
||||
"Re-run ./linux_configuration/scripts/lib/tests/run_all.sh and check_shell_coverage.sh --all to confirm the restored state is coherent."
|
||||
]
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Device classification, partition discovery and layout checks.
|
||||
#
|
||||
# Sourced by mtk_common.sh, which stays the single entry point the
|
||||
# mtk_root scripts source, so their public surface is unchanged.
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Classification
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# classify_device
|
||||
# Sets MTK_DEVICE_CLASS to ULEFONE, PIXEL or UNKNOWN, and MTK_CLASS_REASON to
|
||||
# a human-readable explanation of how it got there.
|
||||
#
|
||||
# ULEFONE requires a positive MediaTek signal AND a vendor/model match - both,
|
||||
# never either, and never "not a Pixel, so presumably the Ulefone". A third
|
||||
# phone attached by accident must land in UNKNOWN.
|
||||
classify_device() {
|
||||
local manufacturer="" model="" platform="" hardware="" device=""
|
||||
local mtk_match=0 ulefone_match=0 pixel_match=0
|
||||
|
||||
manufacturer="$(dev_getprop ro.product.manufacturer)"
|
||||
model="$(dev_getprop ro.product.model)"
|
||||
platform="$(dev_getprop ro.board.platform)"
|
||||
hardware="$(dev_getprop ro.hardware)"
|
||||
device="$(dev_getprop ro.product.device)"
|
||||
|
||||
MTK_PROP_MANUFACTURER="$manufacturer"
|
||||
MTK_PROP_MODEL="$model"
|
||||
MTK_PROP_PLATFORM="$platform"
|
||||
MTK_PROP_HARDWARE="$hardware"
|
||||
MTK_PROP_DEVICE="$device"
|
||||
|
||||
if [[ $platform =~ $MTK_PLATFORM_PATTERN ]] || [[ $hardware =~ $MTK_PLATFORM_PATTERN ]]; then
|
||||
mtk_match=1
|
||||
fi
|
||||
if [[ $manufacturer =~ $ULEFONE_VENDOR_PATTERN ]] || [[ $model =~ $ULEFONE_MODEL_PATTERN ]]; then
|
||||
ulefone_match=1
|
||||
fi
|
||||
if [[ $manufacturer =~ $PIXEL_VENDOR_PATTERN ]] && [[ $model =~ $PIXEL_MODEL_PATTERN ]]; then
|
||||
pixel_match=1
|
||||
fi
|
||||
|
||||
if [[ $mtk_match -eq 1 && $ulefone_match -eq 1 ]]; then
|
||||
MTK_DEVICE_CLASS="ULEFONE"
|
||||
MTK_CLASS_REASON="MediaTek platform [${platform:-unset}/${hardware:-unset}] and Ulefone identity [${manufacturer} ${model}]"
|
||||
elif [[ $pixel_match -eq 1 ]]; then
|
||||
MTK_DEVICE_CLASS="PIXEL"
|
||||
MTK_CLASS_REASON="Google Pixel identity [${manufacturer} ${model}]"
|
||||
else
|
||||
MTK_DEVICE_CLASS="UNKNOWN"
|
||||
MTK_CLASS_REASON="no rule matched: manufacturer=[${manufacturer}] model=[${model}] platform=[${platform}] hardware=[${hardware}]"
|
||||
fi
|
||||
|
||||
export MTK_DEVICE_CLASS MTK_CLASS_REASON
|
||||
export MTK_PROP_MANUFACTURER MTK_PROP_MODEL MTK_PROP_PLATFORM MTK_PROP_HARDWARE MTK_PROP_DEVICE
|
||||
}
|
||||
|
||||
# mtk_explain_classification - why classify_device decided what it did.
|
||||
mtk_explain_classification() {
|
||||
printf 'ro.product.manufacturer = %s\n' "${MTK_PROP_MANUFACTURER:-}"
|
||||
printf 'ro.product.model = %s\n' "${MTK_PROP_MODEL:-}"
|
||||
printf 'ro.product.device = %s\n' "${MTK_PROP_DEVICE:-}"
|
||||
printf 'ro.board.platform = %s\n' "${MTK_PROP_PLATFORM:-}"
|
||||
printf 'ro.hardware = %s\n' "${MTK_PROP_HARDWARE:-}"
|
||||
printf '\n'
|
||||
printf 'MediaTek pattern : %s\n' "$MTK_PLATFORM_PATTERN"
|
||||
printf 'Ulefone vendor : %s\n' "$ULEFONE_VENDOR_PATTERN"
|
||||
printf 'Ulefone model : %s\n' "$ULEFONE_MODEL_PATTERN"
|
||||
printf 'Pixel vendor : %s\n' "$PIXEL_VENDOR_PATTERN"
|
||||
printf 'Pixel model : %s\n' "$PIXEL_MODEL_PATTERN"
|
||||
printf '\n'
|
||||
printf 'Verdict: %s\n' "${MTK_DEVICE_CLASS:-unset}"
|
||||
printf 'Reason : %s\n' "${MTK_CLASS_REASON:-unset}"
|
||||
}
|
||||
|
||||
# mtk_assert_mediatek
|
||||
# A positive assertion, deliberately not "is not a Pixel". Used to gate the
|
||||
# only script that touches partitions.
|
||||
mtk_assert_mediatek() {
|
||||
if [[ ${MTK_PROP_PLATFORM:-} =~ $MTK_PLATFORM_PATTERN ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ ${MTK_PROP_HARDWARE:-} =~ $MTK_PLATFORM_PATTERN ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
@ -1,132 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# mtk_common.sh - Shared helpers for the mtk_root toolkit.
|
||||
#
|
||||
# Source this file; do not execute it directly.
|
||||
#
|
||||
# Every read from a device goes through dev_getprop() or dev_list_by_name().
|
||||
# Those two functions are the ONLY seam between the toolkit and real hardware:
|
||||
# when MTK_ROOT_FIXTURE points at a directory they read text files instead of
|
||||
# talking to adb, which is what makes the whole test suite offline and
|
||||
# hermetic. If you add a new device read, route it through these or the tests
|
||||
# stop meaning anything.
|
||||
# ============================================================================
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Device classification patterns.
|
||||
#
|
||||
# The Ulefone values are GUESSED - the device has not arrived. If it reports
|
||||
# something outside these patterns, classify_device returns UNKNOWN and every
|
||||
# caller refuses to act. That is the intended failure mode, but on day one it
|
||||
# will look like a bug: run `10-recon.sh --explain-classification` to see which
|
||||
# predicate missed, then fix the pattern here. This is the one block that
|
||||
# should need editing when the hardware lands.
|
||||
# ----------------------------------------------------------------------------
|
||||
readonly MTK_PLATFORM_PATTERN='^mt[0-9]{4}'
|
||||
readonly ULEFONE_VENDOR_PATTERN='[Uu]lefone'
|
||||
readonly ULEFONE_MODEL_PATTERN='[Aa]rmor'
|
||||
readonly PIXEL_VENDOR_PATTERN='^[Gg]oogle$'
|
||||
readonly PIXEL_MODEL_PATTERN='[Pp]ixel'
|
||||
|
||||
# Plausible size band for a ramdisk-carrying partition, in bytes. Used by
|
||||
# mtk_sane_ramdisk_size to flag a dump that is obviously wrong - a truncated
|
||||
# read, or a whole-disk grab where a partition was meant.
|
||||
MTK_RAMDISK_MIN_BYTES=$((1024 * 1024))
|
||||
MTK_RAMDISK_MAX_BYTES=$((128 * 1024 * 1024))
|
||||
readonly MTK_RAMDISK_MIN_BYTES MTK_RAMDISK_MAX_BYTES
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Logging. Everything goes to stderr so stdout stays clean for captured data.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
mtk_info() {
|
||||
printf '\033[0;34m[INFO]\033[0m %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
mtk_warn() {
|
||||
printf '\033[0;33m[WARN]\033[0m %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
mtk_error() {
|
||||
printf '\033[0;31m[ERROR]\033[0m %s\n' "$*" >&2
|
||||
}
|
||||
|
||||
mtk_fatal() {
|
||||
printf '\033[0;31m[FATAL]\033[0m %s\n' "$*" >&2
|
||||
exit 1
|
||||
}
|
||||
|
||||
mtk_heading() {
|
||||
printf '\n\033[1;36m== %s ==\033[0m\n' "$*" >&2
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Paths
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# Artifacts live outside the repo: the pre-commit hook blocks every binary
|
||||
# file, and a factory image is several GB. Only text manifests get committed.
|
||||
mtk_cache_dir() {
|
||||
printf '%s' "${MTK_ROOT_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/mtk-root}"
|
||||
}
|
||||
|
||||
mtk_stock_dir() {
|
||||
printf '%s/stock' "$(mtk_cache_dir)"
|
||||
}
|
||||
|
||||
mtk_mtkclient_dir() {
|
||||
printf '%s/mtkclient' "$(mtk_cache_dir)"
|
||||
}
|
||||
|
||||
mtk_venv_python() {
|
||||
printf '%s/venv/bin/python' "$(mtk_mtkclient_dir)"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Device read seam
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# Strip anything a property value has no business containing. adb shell also
|
||||
# returns CRLF line endings, which silently break string comparisons.
|
||||
mtk_sanitize() {
|
||||
printf '%s' "$1" | tr -d '\r' | tr -cd 'A-Za-z0-9 ._:/=+,-'
|
||||
}
|
||||
|
||||
# dev_getprop <property-name>
|
||||
# Prints the value, or nothing if unset. Never fails - an absent property and
|
||||
# an empty one are the same thing to every caller here.
|
||||
dev_getprop() {
|
||||
local prop="$1"
|
||||
local raw=""
|
||||
|
||||
if [[ -n ${MTK_ROOT_FIXTURE:-} ]]; then
|
||||
# Fixture format is one `key=value` per line, matching `getprop` output
|
||||
# once the [brackets] are stripped.
|
||||
raw="$(grep -m1 -- "^${prop}=" "${MTK_ROOT_FIXTURE}/props.txt" 2>/dev/null || true)"
|
||||
raw="${raw#*=}"
|
||||
else
|
||||
raw="$(mtk_adb shell getprop "$prop" 2>/dev/null || true)"
|
||||
fi
|
||||
|
||||
mtk_sanitize "$raw"
|
||||
}
|
||||
|
||||
# dev_list_by_name
|
||||
# Prints one partition name per line, unsorted, as the device reports them.
|
||||
dev_list_by_name() {
|
||||
if [[ -n ${MTK_ROOT_FIXTURE:-} ]]; then
|
||||
tr -d '\r' <"${MTK_ROOT_FIXTURE}/by-name.txt" 2>/dev/null || true
|
||||
return 0
|
||||
fi
|
||||
|
||||
mtk_adb shell 'ls -1 /dev/block/by-name/ 2>/dev/null' 2>/dev/null | tr -d '\r' || true
|
||||
}
|
||||
|
||||
_MTK_LIB_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
# shellcheck source=mtk_device.sh
|
||||
source "$_MTK_LIB_DIR/mtk_device.sh"
|
||||
# shellcheck source=mtk_classify.sh
|
||||
source "$_MTK_LIB_DIR/mtk_classify.sh"
|
||||
# shellcheck source=mtk_partitions.sh
|
||||
source "$_MTK_LIB_DIR/mtk_partitions.sh"
|
||||
@ -1,111 +0,0 @@
|
||||
#!/bin/bash
|
||||
# adb wrappers, device listing, selection and authorisation.
|
||||
#
|
||||
# Sourced by mtk_common.sh, which stays the single entry point the
|
||||
# mtk_root scripts source, so their public surface is unchanged.
|
||||
|
||||
# mtk_adb <args...> - serial-pinned adb, so a second phone plugged in midway
|
||||
# cannot silently become the target.
|
||||
mtk_adb() {
|
||||
adb -s "${MTK_SERIAL:?mtk_select_device must run first}" "$@"
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Device presence and authorization
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# mtk_list_devices - prints "<serial> <state>" per attached device.
|
||||
mtk_list_devices() {
|
||||
adb devices 2>/dev/null |
|
||||
awk 'NR > 1 && $2 ~ /^(device|offline|unauthorized|recovery|sideload)$/ { print $1, $2 }'
|
||||
}
|
||||
|
||||
# mtk_device_state <serial> - device|unauthorized|offline|absent
|
||||
mtk_device_state() {
|
||||
local serial="$1"
|
||||
local state=""
|
||||
|
||||
state="$(mtk_list_devices | awk -v s="$serial" '$1 == s { print $2; exit }')"
|
||||
printf '%s' "${state:-absent}"
|
||||
}
|
||||
|
||||
# mtk_select_device [requested-serial]
|
||||
# Sets MTK_SERIAL. Refuses when the choice is ambiguous rather than picking
|
||||
# one - a wrong guess here targets the wrong phone.
|
||||
mtk_select_device() {
|
||||
local requested="${1:-${MTK_SERIAL:-}}"
|
||||
local -a device_rows=()
|
||||
local -a serials=()
|
||||
local row=""
|
||||
|
||||
if [[ -n ${MTK_ROOT_FIXTURE:-} ]]; then
|
||||
MTK_SERIAL="${MTK_SERIAL:-fixture}"
|
||||
export MTK_SERIAL
|
||||
return 0
|
||||
fi
|
||||
|
||||
mapfile -t device_rows < <(mtk_list_devices)
|
||||
|
||||
if [[ ${#device_rows[@]} -eq 0 ]]; then
|
||||
mtk_error "No device visible to adb."
|
||||
mtk_error "Check: cable seated, USB debugging enabled, and try 'adb devices'."
|
||||
return 1
|
||||
fi
|
||||
|
||||
for row in "${device_rows[@]}"; do
|
||||
serials+=("${row%% *}")
|
||||
done
|
||||
|
||||
if [[ -n $requested ]]; then
|
||||
for row in "${serials[@]}"; do
|
||||
if [[ $row == "$requested" ]]; then
|
||||
MTK_SERIAL="$requested"
|
||||
export MTK_SERIAL
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
mtk_error "Requested serial '${requested}' is not attached. Attached: ${serials[*]}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ ${#serials[@]} -gt 1 ]]; then
|
||||
mtk_error "Multiple devices attached (${serials[*]}); refusing to guess."
|
||||
mtk_error "Re-run with --serial <serial>."
|
||||
return 1
|
||||
fi
|
||||
|
||||
MTK_SERIAL="${serials[0]}"
|
||||
export MTK_SERIAL
|
||||
return 0
|
||||
}
|
||||
|
||||
# mtk_require_authorized
|
||||
# An unauthorized device answers every getprop with an empty string, which
|
||||
# reads exactly like "this device has no properties". Catch it up front so the
|
||||
# report never blames the device for a prompt sitting unanswered on its screen.
|
||||
mtk_require_authorized() {
|
||||
local state=""
|
||||
|
||||
[[ -n ${MTK_ROOT_FIXTURE:-} ]] && return 0
|
||||
|
||||
state="$(mtk_device_state "${MTK_SERIAL}")"
|
||||
case "$state" in
|
||||
device)
|
||||
return 0
|
||||
;;
|
||||
unauthorized)
|
||||
mtk_error "Device ${MTK_SERIAL} is UNAUTHORIZED."
|
||||
mtk_error "The RSA authorization prompt is on the phone's screen - unlock it and tap Allow."
|
||||
mtk_error "If no prompt appears: revoke USB debugging authorizations on the phone, then replug."
|
||||
return 1
|
||||
;;
|
||||
offline)
|
||||
mtk_error "Device ${MTK_SERIAL} is OFFLINE. Replug the cable, or run 'adb kill-server'."
|
||||
return 1
|
||||
;;
|
||||
*)
|
||||
mtk_error "Device ${MTK_SERIAL} is in state '${state}', which this toolkit does not read."
|
||||
return 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
@ -1,195 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Partition discovery, ramdisk carrier and layout expectations.
|
||||
#
|
||||
# Sourced by mtk_common.sh, which stays the single entry point the
|
||||
# mtk_root scripts source, so their public surface is unchanged.
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Partition discovery
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# discover_partitions
|
||||
# Sets MTK_AB_SCHEME, MTK_SLOT_SUFFIX, MTK_RAMDISK_PART, MTK_VBMETA_PART,
|
||||
# MTK_BY_NAME (newline-separated listing).
|
||||
#
|
||||
# The governing rule: never name a partition in a conditional that was not
|
||||
# first observed in the listing. The AOSP rule (devices launching on Android
|
||||
# 13+ carry the ramdisk in init_boot; earlier ones keep it in boot) explains
|
||||
# WHY the preference order below is correct, but the code never consults the
|
||||
# API level to decide - it reads the filesystem. That is what lets one code
|
||||
# path serve both a Pixel (no init_boot -> falls through to boot) and a
|
||||
# 13-launch MediaTek device (init_boot present -> taken) without branching on
|
||||
# device identity.
|
||||
discover_partitions() {
|
||||
local entries="" suffix="" candidate="" name=""
|
||||
|
||||
entries="$(dev_list_by_name)"
|
||||
MTK_BY_NAME="$entries"
|
||||
export MTK_BY_NAME
|
||||
|
||||
if [[ -z $entries ]]; then
|
||||
MTK_AB_SCHEME="unknown"
|
||||
MTK_SLOT_SUFFIX=""
|
||||
MTK_RAMDISK_PART=""
|
||||
MTK_VBMETA_PART=""
|
||||
export MTK_AB_SCHEME MTK_SLOT_SUFFIX MTK_RAMDISK_PART MTK_VBMETA_PART
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Slot scheme comes from observed suffixes, not from a product table.
|
||||
if grep -qE '_a$' <<<"$entries"; then
|
||||
MTK_AB_SCHEME="A/B"
|
||||
else
|
||||
MTK_AB_SCHEME="A-only"
|
||||
fi
|
||||
|
||||
# Prefer the device's own view of the active slot; fall back to _a only
|
||||
# because a dump has to start somewhere, and say so in the report.
|
||||
suffix="$(dev_getprop ro.boot.slot_suffix)"
|
||||
if [[ -z $suffix && $MTK_AB_SCHEME == "A/B" ]]; then
|
||||
suffix="_a"
|
||||
MTK_SLOT_ASSUMED=1
|
||||
else
|
||||
MTK_SLOT_ASSUMED=0
|
||||
fi
|
||||
MTK_SLOT_SUFFIX="$suffix"
|
||||
|
||||
# init_boot first: its presence is decisive. A device that has it keeps the
|
||||
# ramdisk there even though boot also exists, so checking boot first would
|
||||
# pick the wrong partition on exactly the hardware this toolkit is for.
|
||||
MTK_RAMDISK_PART=""
|
||||
for candidate in init_boot boot; do
|
||||
for name in "${candidate}${suffix}" "${candidate}"; do
|
||||
if grep -qxF -- "$name" <<<"$entries"; then
|
||||
MTK_RAMDISK_PART="$name"
|
||||
break 2
|
||||
fi
|
||||
done
|
||||
done
|
||||
|
||||
MTK_VBMETA_PART=""
|
||||
for name in "vbmeta${suffix}" "vbmeta"; do
|
||||
if grep -qxF -- "$name" <<<"$entries"; then
|
||||
MTK_VBMETA_PART="$name"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
export MTK_AB_SCHEME MTK_SLOT_SUFFIX MTK_RAMDISK_PART MTK_VBMETA_PART MTK_SLOT_ASSUMED
|
||||
|
||||
[[ -n $MTK_RAMDISK_PART ]]
|
||||
}
|
||||
|
||||
# mtk_ramdisk_carrier - "init_boot" or "boot", suffix stripped.
|
||||
mtk_ramdisk_carrier() {
|
||||
local part="${MTK_RAMDISK_PART:-}"
|
||||
printf '%s' "${part%_[ab]}"
|
||||
}
|
||||
|
||||
# mtk_check_layout_expectation
|
||||
# Cross-check only. first_api_level >= 33 predicts init_boot; below predicts
|
||||
# boot. A mismatch means either a vendor deviation or a bad read - the human
|
||||
# should look, but the filesystem still wins.
|
||||
mtk_check_layout_expectation() {
|
||||
local api_level="" carrier="" expected=""
|
||||
|
||||
api_level="$(dev_getprop ro.product.first_api_level)"
|
||||
carrier="$(mtk_ramdisk_carrier)"
|
||||
|
||||
[[ -z $api_level || -z $carrier ]] && return 0
|
||||
[[ $api_level =~ ^[0-9]+$ ]] || return 0
|
||||
|
||||
if [[ $api_level -ge 33 ]]; then
|
||||
expected="init_boot"
|
||||
else
|
||||
expected="boot"
|
||||
fi
|
||||
|
||||
if [[ $carrier != "$expected" ]]; then
|
||||
mtk_warn "Layout disagrees with expectation: first_api_level=${api_level} predicts '${expected}', device shows '${carrier}'."
|
||||
mtk_warn "Trusting the device. Verify before flashing anything."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Host environment checks (read-only; install.sh owns the mutating half)
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# mtk_udev_catchall_files
|
||||
# Prints any rules file containing an ACTIVE wildcard-vendor rule granting
|
||||
# world read/write. Such a rule applies to every USB device on the system -
|
||||
# storage, security keys, everything - not just phones.
|
||||
#
|
||||
# Commented-out lines are ignored: --fix-udev disables a rule by commenting it
|
||||
# rather than deleting it, so matching those would leave the check permanently
|
||||
# red after it had already been dealt with. Only *.rules files are considered,
|
||||
# since udev itself skips any other suffix (including our .bak- backups).
|
||||
mtk_udev_catchall_files() {
|
||||
local file=""
|
||||
for file in /etc/udev/rules.d/*.rules; do
|
||||
[[ -f $file ]] || continue
|
||||
if grep -qE '^[^#]*idVendor\}=="\*"[^#]*MODE="0666"|^[^#]*MODE="0666"[^#]*idVendor\}=="\*"' \
|
||||
"$file" 2>/dev/null; then
|
||||
printf '%s\n' "$file"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
# mtk_check_venv - is the mtkclient venv actually usable?
|
||||
# Checks importability, not directory existence: the venv found on this host
|
||||
# in Aug 2026 existed, looked complete, and failed at `import usb` because a
|
||||
# Python upgrade had orphaned it.
|
||||
mtk_check_venv() {
|
||||
local python_bin=""
|
||||
python_bin="$(mtk_venv_python)"
|
||||
|
||||
if [[ ! -x $python_bin ]]; then
|
||||
printf 'missing: no interpreter at %s' "$python_bin"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if ! "$python_bin" -c 'import usb, usb.core, serial' 2>/dev/null; then
|
||||
local err=""
|
||||
err="$("$python_bin" -c 'import usb, usb.core, serial' 2>&1 | tail -1)"
|
||||
printf 'broken: %s' "$err"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf 'ok: %s' "$("$python_bin" --version 2>&1)"
|
||||
return 0
|
||||
}
|
||||
|
||||
# mtk_sha256 <file>
|
||||
mtk_sha256() {
|
||||
sha256sum "$1" | awk '{ print $1 }'
|
||||
}
|
||||
|
||||
# mtk_file_size <file>
|
||||
mtk_file_size() {
|
||||
stat -c '%s' "$1"
|
||||
}
|
||||
|
||||
# mtk_sane_ramdisk_size <bytes>
|
||||
# A dump outside this band is not necessarily wrong, but it is worth stopping
|
||||
# for: an empty or truncated read and an accidental whole-disk grab both look
|
||||
# like success to mtkclient's exit code.
|
||||
mtk_sane_ramdisk_size() {
|
||||
local size="$1"
|
||||
|
||||
if [[ ! $size =~ ^[0-9]+$ ]]; then
|
||||
printf 'size is not a number: %s' "$size"
|
||||
return 1
|
||||
fi
|
||||
if [[ $size -lt $MTK_RAMDISK_MIN_BYTES ]]; then
|
||||
printf 'implausibly small: %s bytes (< %s)' "$size" "$MTK_RAMDISK_MIN_BYTES"
|
||||
return 1
|
||||
fi
|
||||
if [[ $size -gt $MTK_RAMDISK_MAX_BYTES ]]; then
|
||||
printf 'implausibly large: %s bytes (> %s)' "$size" "$MTK_RAMDISK_MAX_BYTES"
|
||||
return 1
|
||||
fi
|
||||
printf '%s bytes' "$size"
|
||||
return 0
|
||||
}
|
||||
@ -3,13 +3,14 @@
|
||||
#
|
||||
# Sourced, not executed.
|
||||
#
|
||||
# The name is historical: this fixture was written for the mtk_* libraries,
|
||||
# which have since moved to github.com/kuhyx/mtk-root along with their suites.
|
||||
# It stays because test_common_datetime.sh sources it, and renaming it would
|
||||
# churn that file for no behavioural gain.
|
||||
#
|
||||
# WHAT IS EXERCISED
|
||||
# The pure decision logic and everything reachable through the
|
||||
# MTK_ROOT_FIXTURE seam that mtk_common.sh documents as its only boundary
|
||||
# with real hardware: classify_device, discover_partitions,
|
||||
# mtk_ramdisk_carrier, mtk_check_layout_expectation, mtk_assert_mediatek,
|
||||
# mtk_sanitize, mtk_sane_ramdisk_size, mtk_select_device/require_authorized
|
||||
# under a fixture, plus the string helpers in common_datetime.sh.
|
||||
# The pure decision logic in the libraries that remain here — currently the
|
||||
# string and clock helpers in common_datetime.sh.
|
||||
#
|
||||
# WHAT IS DELIBERATELY NOT EXERCISED, AND WHY
|
||||
# The coverage gate only checks that this directory HAS a run_all.sh, so it
|
||||
@ -22,11 +23,7 @@
|
||||
# - collapse_mounts — calls `umount -l` on a live path.
|
||||
# - install_missing_pacman_packages / require_imagemagick / notify —
|
||||
# effecting code (pacman, desktop notifications).
|
||||
# - mtk_udev_catchall_files — globs /etc/udev/rules.d with no seam, so it
|
||||
# reads this host's real rules; the result differs between here and CI.
|
||||
# - mtk_sha256 / mtk_file_size — one-line wrappers over sha256sum/stat.
|
||||
# This matches Phase 1's measured pattern: decision logic reaches ~90%, the
|
||||
# effecting code reaches 0%.
|
||||
# The measured pattern holds: decision logic reaches ~90%, effecting code 0%.
|
||||
#
|
||||
# TIME AND HOST DEPENDENCE
|
||||
# This suite runs in shell-tests.yml and again on every pre-push via
|
||||
|
||||
@ -1,15 +1,16 @@
|
||||
#!/usr/bin/env bash
|
||||
# Runs every linux_configuration/scripts/lib test.
|
||||
#
|
||||
# The glob is test_*.sh, NOT one prefix per subject: this directory holds
|
||||
# suites for several libraries (mtk_*, common_datetime, ...), and a prefix
|
||||
# glob silently skips every suite added after it — including in the CI
|
||||
# discovery step in shell-tests.yml, which runs this file.
|
||||
# The glob is test_*.sh, NOT one prefix per subject: a prefix glob silently
|
||||
# skips every suite added after it — including in the CI discovery step in
|
||||
# shell-tests.yml, which runs this file. Only common_datetime is left here
|
||||
# since the mtk_* libraries moved to github.com/kuhyx/mtk-root, but the glob
|
||||
# stays generic for the same reason it was generic before.
|
||||
#
|
||||
# This file's existence is what meta/scripts/check_shell_coverage.sh checks:
|
||||
# is_covered() tests for tests/run_all.sh and nothing more, so an empty one
|
||||
# here would mark all nine libraries in the parent directory covered while
|
||||
# testing nothing. The suites it runs, and the gaps they leave, are named in
|
||||
# here would mark every library in the parent directory covered while testing
|
||||
# nothing. The suites it runs, and the gaps they leave, are named in
|
||||
# mtk_harness.sh's header.
|
||||
set -euo pipefail
|
||||
|
||||
@ -22,8 +23,8 @@ for t in "$HERE"/test_*.sh; do
|
||||
# hide the state of the others, then fail the run as a whole.
|
||||
# Invoked directly, NOT as `bash "$t"`: under kcov the latter instruments
|
||||
# bash itself and reports zero lines for every lib. A fresh process per
|
||||
# suite also matters here, because mtk_common.sh declares readonly
|
||||
# patterns and sourcing it twice in one shell aborts under set -e.
|
||||
# suite also matters here: a library that declares readonly values aborts
|
||||
# under set -e if it is sourced twice in one shell.
|
||||
"$t" || rc=1
|
||||
done
|
||||
|
||||
|
||||
@ -1,120 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Covers classify_device, mtk_assert_mediatek, mtk_explain_classification
|
||||
# and mtk_sanitize, all through the MTK_ROOT_FIXTURE seam.
|
||||
#
|
||||
# The load-bearing assertion here is the THIRD PHONE case. classify_device's
|
||||
# contract is that ULEFONE requires a MediaTek signal AND a vendor/model
|
||||
# match — both, never either — so an unrelated MediaTek handset must land in
|
||||
# UNKNOWN rather than being treated as the Ulefone. Every caller refuses to
|
||||
# act on UNKNOWN, and that is what stops this toolkit flashing a stranger's
|
||||
# phone. A rule rewritten to `||` would still pass the ULEFONE and PIXEL
|
||||
# cases below; only this one catches it.
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=mtk_harness.sh
|
||||
source "$HERE/mtk_harness.sh"
|
||||
|
||||
_t_sandbox
|
||||
# shellcheck source=../mtk_common.sh
|
||||
source "$HERE/../mtk_common.sh"
|
||||
|
||||
# --- classify_device -------------------------------------------------------
|
||||
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture ulefone \
|
||||
'ro.product.manufacturer=Ulefone
|
||||
ro.product.model=Armor 26 Ultra
|
||||
ro.board.platform=mt6989
|
||||
ro.hardware=mt6989
|
||||
ro.product.device=Armor26Ultra' 'boot_a')"
|
||||
export MTK_ROOT_FIXTURE
|
||||
classify_device
|
||||
_t_is "ULEFONE" "$MTK_DEVICE_CLASS" "MediaTek platform + Ulefone vendor -> ULEFONE"
|
||||
_t_in "MediaTek platform" "$MTK_CLASS_REASON" "ULEFONE reason names the platform signal"
|
||||
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture pixel \
|
||||
'ro.product.manufacturer=Google
|
||||
ro.product.model=Pixel 6a
|
||||
ro.board.platform=gs101
|
||||
ro.hardware=oriole
|
||||
ro.product.device=bluejay' 'boot_a')"
|
||||
classify_device
|
||||
_t_is "PIXEL" "$MTK_DEVICE_CLASS" "Google + Pixel identity -> PIXEL"
|
||||
|
||||
# A MediaTek phone that is NOT the Ulefone. The single most important case:
|
||||
# "not a Pixel, so presumably the Ulefone" is exactly the reasoning the
|
||||
# implementation refuses, and it is what would target the wrong hardware.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture third_party \
|
||||
'ro.product.manufacturer=Xiaomi
|
||||
ro.product.model=Redmi Note 12
|
||||
ro.board.platform=mt6833
|
||||
ro.hardware=mt6833
|
||||
ro.product.device=tapas' 'boot_a')"
|
||||
classify_device
|
||||
_t_is "UNKNOWN" "$MTK_DEVICE_CLASS" "MediaTek but wrong vendor -> UNKNOWN, not ULEFONE"
|
||||
|
||||
# Ulefone identity with no MediaTek signal is equally not enough.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture ulefone_no_mtk \
|
||||
'ro.product.manufacturer=Ulefone
|
||||
ro.product.model=Armor 26 Ultra
|
||||
ro.board.platform=sm8650
|
||||
ro.hardware=qcom
|
||||
ro.product.device=Armor26Ultra' 'boot_a')"
|
||||
classify_device
|
||||
_t_is "UNKNOWN" "$MTK_DEVICE_CLASS" "Ulefone identity without MediaTek -> UNKNOWN"
|
||||
|
||||
# An unauthorized device answers every getprop with an empty string. That must
|
||||
# not resolve to a class.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture empty '' 'boot_a')"
|
||||
classify_device
|
||||
_t_is "UNKNOWN" "$MTK_DEVICE_CLASS" "no properties at all -> UNKNOWN"
|
||||
_t_in "no rule matched" "$MTK_CLASS_REASON" "UNKNOWN reason explains which reads were empty"
|
||||
|
||||
# --- mtk_assert_mediatek ---------------------------------------------------
|
||||
|
||||
# A positive assertion, deliberately not "is not a Pixel". It gates the only
|
||||
# script that touches partitions, so both directions are asserted.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture assert_mtk \
|
||||
'ro.board.platform=mt6989
|
||||
ro.hardware=mt6989' 'boot_a')"
|
||||
classify_device
|
||||
_t_rc 0 "mtk_assert_mediatek accepts an mt#### platform" mtk_assert_mediatek
|
||||
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture assert_qcom \
|
||||
'ro.board.platform=sm8650
|
||||
ro.hardware=qcom' 'boot_a')"
|
||||
classify_device
|
||||
_t_rc 1 "mtk_assert_mediatek rejects a non-MediaTek platform" mtk_assert_mediatek
|
||||
|
||||
# hardware alone is enough — some devices report the SoC there and leave
|
||||
# ro.board.platform unset.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture assert_hw_only 'ro.hardware=mt8183' 'boot_a')"
|
||||
classify_device
|
||||
_t_rc 0 "mtk_assert_mediatek accepts a MediaTek ro.hardware alone" mtk_assert_mediatek
|
||||
|
||||
# --- mtk_explain_classification --------------------------------------------
|
||||
|
||||
explain_out="$(mtk_explain_classification 2>&1)"
|
||||
_t_in "Verdict:" "$explain_out" "explain output states a verdict"
|
||||
_t_in "mt8183" "$explain_out" "explain output echoes the property it read"
|
||||
|
||||
# --- mtk_sanitize ----------------------------------------------------------
|
||||
|
||||
# adb shell returns CRLF, which silently breaks string comparisons — the
|
||||
# reason this function exists at all.
|
||||
_t_is "mt6989" "$(mtk_sanitize "$(printf 'mt6989\r')")" "sanitize strips a trailing CR"
|
||||
_t_is "Armor 26 Ultra" "$(mtk_sanitize 'Armor 26 Ultra')" "sanitize keeps spaces and digits"
|
||||
# tr -cd DELETES disallowed bytes rather than truncating at the first one, so
|
||||
# the alphanumerics inside a substitution survive as inert text. What matters
|
||||
# is that $ ( ) ` ; & | cannot come back out.
|
||||
# The metacharacters are assembled from \-escapes rather than written inside
|
||||
# single quotes, so that "these must not expand" is stated in the code rather
|
||||
# than left to the reader (and to shellcheck) to infer.
|
||||
hostile="rm -rf \$(whoami)\`id\`; reboot & echo|x"
|
||||
sanitized="$(mtk_sanitize "$hostile")"
|
||||
_t_is "rm -rf whoamiid reboot echox" "$sanitized" "sanitize deletes shell metacharacters"
|
||||
for meta in '$' '(' ')' '`' ';' '&' '|'; do
|
||||
_t_is "" "${sanitized//[^"$meta"]/}" "sanitize leaves no '${meta}' behind"
|
||||
done
|
||||
|
||||
_t_done "test_mtk_classify.sh"
|
||||
@ -1,152 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Covers mtk_list_devices, mtk_device_state, mtk_select_device and
|
||||
# mtk_require_authorized.
|
||||
#
|
||||
# The load-bearing case is REFUSING TO GUESS. With two phones attached and no
|
||||
# --serial, mtk_select_device must fail rather than pick one: a wrong choice
|
||||
# here points a partition-flashing toolkit at the wrong device. A version
|
||||
# that took serials[0] would pass every other assertion in this file.
|
||||
#
|
||||
# adb is shimmed onto PATH rather than mocked in-process, because
|
||||
# mtk_list_devices calls the binary directly. _t_adb_says writes the shim's
|
||||
# canned `adb devices` output; nothing here talks to real hardware, and the
|
||||
# shim refuses any subcommand the tests do not expect.
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=mtk_harness.sh
|
||||
source "$HERE/mtk_harness.sh"
|
||||
|
||||
_t_sandbox
|
||||
# shellcheck source=../mtk_common.sh
|
||||
source "$HERE/../mtk_common.sh"
|
||||
|
||||
# --- adb shim --------------------------------------------------------------
|
||||
|
||||
# Narrow PATH to a shim dir. Every external these functions reach for must be
|
||||
# seeded here: a missing one aborts the suite under `set -e` with no stderr.
|
||||
_t_shim_dir="$T_TMP/bin"
|
||||
mkdir -p "$_t_shim_dir"
|
||||
cat >"$_t_shim_dir/adb" <<'SHIM'
|
||||
#!/usr/bin/env bash
|
||||
# Test double. Prints the canned `adb devices` table and nothing else; any
|
||||
# other subcommand is a bug in the test, so say so loudly rather than
|
||||
# returning an empty string that would read as "no devices".
|
||||
if [[ ${1:-} == "devices" ]]; then
|
||||
cat "${T_ADB_DEVICES:?}"
|
||||
exit 0
|
||||
fi
|
||||
printf 'adb shim: unexpected invocation: %s\n' "$*" >&2
|
||||
exit 97
|
||||
SHIM
|
||||
chmod +x "$_t_shim_dir/adb"
|
||||
for _t_ext in awk mapfile cat printf; do
|
||||
_t_bin="$(command -v "$_t_ext" 2>/dev/null || true)"
|
||||
[[ -n $_t_bin ]] && ln -sf "$_t_bin" "$_t_shim_dir/$_t_ext"
|
||||
done
|
||||
PATH="$_t_shim_dir:/usr/bin:/bin"
|
||||
export PATH
|
||||
|
||||
T_ADB_DEVICES="$T_TMP/adb_devices.txt"
|
||||
export T_ADB_DEVICES
|
||||
|
||||
_t_adb_says() {
|
||||
printf 'List of devices attached\n%s\n' "$1" >"$T_ADB_DEVICES"
|
||||
}
|
||||
|
||||
# --- mtk_list_devices ------------------------------------------------------
|
||||
|
||||
_t_adb_says 'ABC123 device'
|
||||
_t_is "ABC123 device" "$(mtk_list_devices)" "one attached device is listed with its state"
|
||||
|
||||
_t_adb_says 'ABC123 device
|
||||
DEF456 unauthorized'
|
||||
_t_is "2" "$(mtk_list_devices | wc -l)" "both attached devices are listed"
|
||||
|
||||
# A blank table is what an empty `adb devices` looks like.
|
||||
_t_adb_says ''
|
||||
_t_is "" "$(mtk_list_devices)" "no attached devices lists nothing"
|
||||
|
||||
# --- mtk_device_state ------------------------------------------------------
|
||||
|
||||
_t_adb_says 'ABC123 device
|
||||
DEF456 unauthorized
|
||||
GHI789 offline'
|
||||
_t_is "device" "$(mtk_device_state ABC123)" "an authorized device reports 'device'"
|
||||
_t_is "unauthorized" "$(mtk_device_state DEF456)" "an unauthorized device reports so"
|
||||
_t_is "offline" "$(mtk_device_state GHI789)" "an offline device reports so"
|
||||
_t_is "absent" "$(mtk_device_state NOPE000)" "an unattached serial reports 'absent'"
|
||||
|
||||
# --- mtk_select_device -----------------------------------------------------
|
||||
|
||||
_t_adb_says 'ABC123 device'
|
||||
unset MTK_SERIAL
|
||||
_t_rc 0 "a single attached device is selected" mtk_select_device
|
||||
unset MTK_SERIAL
|
||||
mtk_select_device
|
||||
_t_is "ABC123" "$MTK_SERIAL" "the selected serial is the attached one"
|
||||
|
||||
# Two devices, no request: must refuse.
|
||||
_t_adb_says 'ABC123 device
|
||||
DEF456 device'
|
||||
unset MTK_SERIAL
|
||||
_t_rc 1 "two devices with no --serial refuses to guess" mtk_select_device
|
||||
unset MTK_SERIAL
|
||||
refuse_out="$(mtk_select_device 2>&1 || true)"
|
||||
_t_in "refusing to guess" "$refuse_out" "the refusal says why"
|
||||
_t_in "--serial" "$refuse_out" "the refusal names the way out"
|
||||
|
||||
# Two devices WITH a request: the named one is honoured.
|
||||
unset MTK_SERIAL
|
||||
mtk_select_device DEF456
|
||||
_t_is "DEF456" "$MTK_SERIAL" "an explicit serial disambiguates"
|
||||
|
||||
# A requested serial that is not attached must fail, not fall back.
|
||||
unset MTK_SERIAL
|
||||
_t_rc 1 "a requested serial that is absent fails" mtk_select_device ZZZ999
|
||||
unset MTK_SERIAL
|
||||
_t_in "is not attached" "$(mtk_select_device ZZZ999 2>&1 || true)" \
|
||||
"the absent-serial error names the problem"
|
||||
|
||||
# No devices at all.
|
||||
_t_adb_says ''
|
||||
unset MTK_SERIAL
|
||||
_t_rc 1 "no attached devices fails" mtk_select_device
|
||||
unset MTK_SERIAL
|
||||
_t_in "No device visible" "$(mtk_select_device 2>&1 || true)" "the no-device error is actionable"
|
||||
|
||||
# --- mtk_require_authorized ------------------------------------------------
|
||||
|
||||
_t_adb_says 'ABC123 device
|
||||
DEF456 unauthorized
|
||||
GHI789 offline'
|
||||
|
||||
MTK_SERIAL=ABC123
|
||||
_t_rc 0 "an authorized device passes the gate" mtk_require_authorized
|
||||
|
||||
# The case this function exists for: an unauthorized device answers every
|
||||
# getprop with an empty string, which reads exactly like "no properties".
|
||||
MTK_SERIAL=DEF456
|
||||
_t_rc 1 "an unauthorized device is caught up front" mtk_require_authorized
|
||||
_t_in "UNAUTHORIZED" "$(mtk_require_authorized 2>&1 || true)" \
|
||||
"the unauthorized error blames the prompt, not the device"
|
||||
|
||||
MTK_SERIAL=GHI789
|
||||
_t_rc 1 "an offline device is caught" mtk_require_authorized
|
||||
_t_in "OFFLINE" "$(mtk_require_authorized 2>&1 || true)" "the offline error is actionable"
|
||||
|
||||
MTK_SERIAL=NOPE000
|
||||
_t_rc 1 "an absent device is caught" mtk_require_authorized
|
||||
|
||||
# Under a fixture both selection and authorization are bypassed entirely,
|
||||
# which is what keeps the rest of this directory's suites offline.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture selectable '' 'boot')"
|
||||
export MTK_ROOT_FIXTURE
|
||||
unset MTK_SERIAL
|
||||
_t_rc 0 "a fixture selects without adb" mtk_select_device
|
||||
unset MTK_SERIAL
|
||||
mtk_select_device
|
||||
_t_is "fixture" "$MTK_SERIAL" "the fixture serial is a placeholder"
|
||||
_t_rc 0 "a fixture is always authorized" mtk_require_authorized
|
||||
|
||||
_t_done "test_mtk_device.sh"
|
||||
@ -1,142 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Covers discover_partitions, mtk_ramdisk_carrier,
|
||||
# mtk_check_layout_expectation, mtk_sane_ramdisk_size and mtk_check_venv.
|
||||
#
|
||||
# The load-bearing case is INIT_BOOT WINS OVER BOOT. A device launched on
|
||||
# Android 13+ carries the ramdisk in init_boot even though boot also exists,
|
||||
# so a loop that checked boot first would pick the wrong partition on exactly
|
||||
# the hardware this toolkit targets — and would do it silently, because both
|
||||
# names are present and both reads succeed.
|
||||
#
|
||||
# The second is that discover_partitions consults the FILESYSTEM, never the
|
||||
# API level. mtk_check_layout_expectation may disagree and warn, but it must
|
||||
# not change the answer.
|
||||
set -euo pipefail
|
||||
|
||||
HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
# shellcheck source=mtk_harness.sh
|
||||
source "$HERE/mtk_harness.sh"
|
||||
|
||||
_t_sandbox
|
||||
# shellcheck source=../mtk_common.sh
|
||||
source "$HERE/../mtk_common.sh"
|
||||
|
||||
# --- discover_partitions ---------------------------------------------------
|
||||
|
||||
# A/B device with init_boot: the ramdisk lives in init_boot, not boot.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture ab_initboot 'ro.boot.slot_suffix=_a' \
|
||||
'boot_a
|
||||
boot_b
|
||||
init_boot_a
|
||||
init_boot_b
|
||||
vbmeta_a
|
||||
vbmeta_b
|
||||
userdata')"
|
||||
export MTK_ROOT_FIXTURE
|
||||
_t_rc 0 "discover_partitions succeeds when a ramdisk carrier exists" discover_partitions
|
||||
discover_partitions
|
||||
_t_is "A/B" "$MTK_AB_SCHEME" "an _a suffix in the listing means A/B"
|
||||
_t_is "_a" "$MTK_SLOT_SUFFIX" "slot suffix comes from ro.boot.slot_suffix"
|
||||
_t_is "init_boot_a" "$MTK_RAMDISK_PART" "init_boot wins over boot when both exist"
|
||||
_t_is "vbmeta_a" "$MTK_VBMETA_PART" "vbmeta picks up the active slot suffix"
|
||||
_t_is "init_boot" "$(mtk_ramdisk_carrier)" "carrier strips the slot suffix"
|
||||
_t_is "0" "$MTK_SLOT_ASSUMED" "a reported slot suffix is not an assumption"
|
||||
|
||||
# A-only device with no init_boot: must fall through to boot.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture aonly_boot '' \
|
||||
'boot
|
||||
recovery
|
||||
vbmeta
|
||||
userdata')"
|
||||
discover_partitions
|
||||
_t_is "A-only" "$MTK_AB_SCHEME" "no _a suffix means A-only"
|
||||
_t_is "" "$MTK_SLOT_SUFFIX" "A-only has no slot suffix"
|
||||
_t_is "boot" "$MTK_RAMDISK_PART" "falls through to boot when init_boot is absent"
|
||||
_t_is "boot" "$(mtk_ramdisk_carrier)" "carrier of an unsuffixed boot is boot"
|
||||
|
||||
# A/B device whose slot_suffix is unreadable. The code falls back to _a
|
||||
# because a dump has to start somewhere, but must FLAG that it guessed.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture ab_no_suffix '' \
|
||||
'boot_a
|
||||
boot_b
|
||||
vbmeta_a
|
||||
vbmeta_b')"
|
||||
discover_partitions
|
||||
_t_is "_a" "$MTK_SLOT_SUFFIX" "A/B with no reported suffix falls back to _a"
|
||||
_t_is "1" "$MTK_SLOT_ASSUMED" "the _a fallback is recorded as an assumption"
|
||||
_t_is "boot_a" "$MTK_RAMDISK_PART" "assumed suffix still resolves a carrier"
|
||||
|
||||
# An empty listing is what an unauthorized or absent device produces. It must
|
||||
# fail rather than reporting a plausible-looking empty layout.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture no_partitions '' '')"
|
||||
_t_rc 1 "discover_partitions fails on an empty by-name listing" discover_partitions
|
||||
discover_partitions || true
|
||||
_t_is "unknown" "$MTK_AB_SCHEME" "an empty listing yields an unknown scheme"
|
||||
_t_is "" "$MTK_RAMDISK_PART" "an empty listing names no ramdisk partition"
|
||||
|
||||
# A listing with neither boot nor init_boot: non-empty, but no carrier.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture no_carrier '' \
|
||||
'system
|
||||
vendor
|
||||
userdata')"
|
||||
_t_rc 1 "discover_partitions fails when no carrier is present" discover_partitions
|
||||
|
||||
# --- mtk_check_layout_expectation ------------------------------------------
|
||||
|
||||
# first_api_level >= 33 predicts init_boot. Agreement is quiet.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture api34_initboot \
|
||||
'ro.boot.slot_suffix=_a
|
||||
ro.product.first_api_level=34' \
|
||||
'boot_a
|
||||
init_boot_a')"
|
||||
discover_partitions
|
||||
_t_rc 0 "api 34 + init_boot carrier agrees" mtk_check_layout_expectation
|
||||
|
||||
# Disagreement warns and returns 1 — but the device still wins.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture api34_boot_only \
|
||||
'ro.boot.slot_suffix=_a
|
||||
ro.product.first_api_level=34' \
|
||||
'boot_a
|
||||
vbmeta_a')"
|
||||
discover_partitions
|
||||
_t_rc 1 "api 34 + boot carrier disagrees and says so" mtk_check_layout_expectation
|
||||
_t_is "boot_a" "$MTK_RAMDISK_PART" "a disagreeing expectation does NOT change the answer"
|
||||
warn_out="$(mtk_check_layout_expectation 2>&1 || true)"
|
||||
_t_in "Trusting the device" "$warn_out" "the warning states the filesystem wins"
|
||||
|
||||
# Below 33 predicts boot.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture api30_boot 'ro.product.first_api_level=30' 'boot')"
|
||||
discover_partitions
|
||||
_t_rc 0 "api 30 + boot carrier agrees" mtk_check_layout_expectation
|
||||
|
||||
# An unreadable or non-numeric api level is not an error — it is simply no
|
||||
# cross-check. This must not be mistaken for a disagreement.
|
||||
MTK_ROOT_FIXTURE="$(_t_fixture api_missing '' 'boot')"
|
||||
discover_partitions
|
||||
_t_rc 0 "an absent api level skips the cross-check" mtk_check_layout_expectation
|
||||
|
||||
# --- mtk_sane_ramdisk_size -------------------------------------------------
|
||||
|
||||
# Both an empty read and a whole-disk grab look like success to mtkclient's
|
||||
# exit code, which is the entire reason this band exists.
|
||||
_t_rc 1 "zero bytes is implausible" mtk_sane_ramdisk_size 0
|
||||
_t_in "implausibly small" "$(mtk_sane_ramdisk_size 0 || true)" "small failure says which way"
|
||||
_t_rc 1 "a truncated read is implausible" mtk_sane_ramdisk_size 1024
|
||||
_t_rc 0 "8 MiB is a plausible ramdisk" mtk_sane_ramdisk_size $((8 * 1024 * 1024))
|
||||
_t_rc 0 "the lower bound itself is accepted" mtk_sane_ramdisk_size $((1024 * 1024))
|
||||
_t_rc 0 "the upper bound itself is accepted" mtk_sane_ramdisk_size $((128 * 1024 * 1024))
|
||||
_t_rc 1 "a whole-disk grab is implausible" mtk_sane_ramdisk_size $((4 * 1024 * 1024 * 1024))
|
||||
_t_in "implausibly large" "$(mtk_sane_ramdisk_size $((4 * 1024 * 1024 * 1024)) || true)" \
|
||||
"large failure says which way"
|
||||
# `stat` failing yields an empty string, not a number.
|
||||
_t_rc 1 "a non-numeric size is rejected" mtk_sane_ramdisk_size "not-a-number"
|
||||
_t_rc 1 "an empty size is rejected" mtk_sane_ramdisk_size ""
|
||||
|
||||
# --- mtk_check_venv --------------------------------------------------------
|
||||
|
||||
# MTK_ROOT_CACHE points at an empty tmpdir, so this takes the deterministic
|
||||
# "missing" branch on any host, CI included.
|
||||
_t_rc 1 "an absent venv is reported as missing" mtk_check_venv
|
||||
_t_in "missing:" "$(mtk_check_venv || true)" "the missing branch names the interpreter path"
|
||||
|
||||
_t_done "test_mtk_partitions.sh"
|
||||
@ -1,222 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# 00-preflight.sh - Verify the host toolkit is intact. No device required.
|
||||
#
|
||||
# Run this first on the day the phone arrives, to confirm nothing rotted while
|
||||
# it was in transit. Every check reports independently; the script exits
|
||||
# non-zero if any FAIL, so it is usable as a gate.
|
||||
#
|
||||
# This checks the HOST only. It cannot confirm that udev actually grants
|
||||
# access to a MediaTek device in BROM mode - that needs the hardware.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||
readonly SCRIPT_DIR
|
||||
readonly SCRIPT_NAME="${0##*/}"
|
||||
|
||||
# shellcheck source=../lib/mtk_common.sh
|
||||
source "$SCRIPT_DIR/../lib/mtk_common.sh"
|
||||
|
||||
readonly UDEV_RULES_FILE="/etc/udev/rules.d/60-mtk-root.rules"
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
WARN=0
|
||||
|
||||
pass() {
|
||||
PASS=$((PASS + 1))
|
||||
printf ' \033[0;32m[PASS]\033[0m %-34s %s\n' "$1" "${2:-}"
|
||||
}
|
||||
|
||||
fail() {
|
||||
FAIL=$((FAIL + 1))
|
||||
printf ' \033[0;31m[FAIL]\033[0m %-34s %s\n' "$1" "${2:-}"
|
||||
}
|
||||
|
||||
warn() {
|
||||
WARN=$((WARN + 1))
|
||||
printf ' \033[0;33m[WARN]\033[0m %-34s %s\n' "$1" "${2:-}"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $SCRIPT_NAME [--help]
|
||||
|
||||
Verifies host tooling for the mtk_root toolkit. No device needed.
|
||||
Exits non-zero if any check fails.
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
check_tools() {
|
||||
mtk_heading "Tools"
|
||||
|
||||
local -a required=(adb fastboot python3 git sha256sum)
|
||||
local tool="" version=""
|
||||
|
||||
for tool in "${required[@]}"; do
|
||||
if ! command -v "$tool" >/dev/null 2>&1; then
|
||||
fail "$tool" "not installed - run install.sh"
|
||||
continue
|
||||
fi
|
||||
# Invoke it, don't just locate it: a binary on PATH that cannot run is a
|
||||
# failure mode `command -v` reports as success.
|
||||
#
|
||||
# Each capture is wrapped in `|| true` and reads the whole stream before
|
||||
# slicing. Piping straight into `head -1` closes the pipe early, and under
|
||||
# `set -o pipefail` the resulting SIGPIPE kills this script outright.
|
||||
version=""
|
||||
case "$tool" in
|
||||
adb) version="$(adb version 2>/dev/null || true)" ;;
|
||||
fastboot) version="$(fastboot --version 2>/dev/null || true)" ;;
|
||||
python3) version="$(python3 -V 2>&1 || true)" ;;
|
||||
git) version="$(git --version 2>/dev/null || true)" ;;
|
||||
*) version="$("$tool" --version 2>/dev/null || true)" ;;
|
||||
esac
|
||||
version="${version%%$'\n'*}"
|
||||
if [[ -z $version ]]; then
|
||||
fail "$tool" "present but did not respond to a version query"
|
||||
else
|
||||
pass "$tool" "$version"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_groups() {
|
||||
mtk_heading "Group membership"
|
||||
|
||||
local -a wanted=(plugdev adbusers)
|
||||
local group="" active_groups=""
|
||||
active_groups=" $(id -nG) "
|
||||
|
||||
for group in "${wanted[@]}"; do
|
||||
if ! getent group "$group" >/dev/null 2>&1; then
|
||||
warn "$group" "group does not exist on this system"
|
||||
continue
|
||||
fi
|
||||
if [[ $active_groups == *" $group "* ]]; then
|
||||
pass "$group" "active in this session"
|
||||
elif id -nG "$USER" 2>/dev/null | grep -qw "$group"; then
|
||||
# Membership that the current login session predates. The rule is real
|
||||
# but will not take effect until re-login, and USB access will fail in
|
||||
# a way that looks like a udev problem.
|
||||
warn "$group" "member, but not in this session - log out and back in"
|
||||
else
|
||||
fail "$group" "not a member - run install.sh"
|
||||
fi
|
||||
done
|
||||
}
|
||||
|
||||
check_udev() {
|
||||
mtk_heading "udev rules"
|
||||
|
||||
if [[ -f $UDEV_RULES_FILE ]]; then
|
||||
if command -v udevadm >/dev/null 2>&1 && udevadm verify --help >/dev/null 2>&1; then
|
||||
# A real syntax verdict. `udevadm control --reload` exits 0 regardless,
|
||||
# so it cannot be used to validate anything.
|
||||
if udevadm verify "$UDEV_RULES_FILE" >/dev/null 2>&1; then
|
||||
pass "60-mtk-root.rules" "present, udevadm verify clean"
|
||||
else
|
||||
fail "60-mtk-root.rules" "present but udevadm verify REJECTED it"
|
||||
fi
|
||||
else
|
||||
warn "60-mtk-root.rules" "present; udevadm verify unavailable, syntax unchecked"
|
||||
fi
|
||||
else
|
||||
fail "60-mtk-root.rules" "missing - run install.sh"
|
||||
fi
|
||||
|
||||
# A wildcard-vendor world-writable rule is a genuine finding, not a nit: it
|
||||
# applies to every USB device attached to this machine, not just phones.
|
||||
local catchall=""
|
||||
catchall="$(mtk_udev_catchall_files)"
|
||||
if [[ -n $catchall ]]; then
|
||||
fail "no world-writable catch-all" "found in: $(tr '\n' ' ' <<<"$catchall")"
|
||||
printf ' A rule matching idVendor=="*" with MODE="0666" grants every\n'
|
||||
printf ' local process read/write access to EVERY USB device, including\n'
|
||||
printf ' storage and security keys. Narrow it: install.sh --fix-udev\n'
|
||||
else
|
||||
pass "no world-writable catch-all" "no wildcard 0666 rule found"
|
||||
fi
|
||||
}
|
||||
|
||||
check_venv() {
|
||||
mtk_heading "mtkclient"
|
||||
|
||||
local venv_status=""
|
||||
if venv_status="$(mtk_check_venv)"; then
|
||||
pass "venv importable" "$venv_status"
|
||||
else
|
||||
fail "venv importable" "$venv_status"
|
||||
printf ' The venv existing is not enough - it must be able to import\n'
|
||||
printf ' usb/serial. A Python upgrade orphans a venv while leaving it\n'
|
||||
printf ' looking complete. Rebuild: install.sh\n'
|
||||
fi
|
||||
|
||||
local clone_dir=""
|
||||
clone_dir="$(mtk_mtkclient_dir)"
|
||||
if [[ -d "$clone_dir/.git" ]]; then
|
||||
local rev=""
|
||||
rev="$(git -C "$clone_dir" rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||
pass "mtkclient checkout" "rev ${rev}"
|
||||
else
|
||||
fail "mtkclient checkout" "not a git clone at ${clone_dir} - run install.sh"
|
||||
fi
|
||||
}
|
||||
|
||||
check_cache() {
|
||||
mtk_heading "Workspace"
|
||||
|
||||
local cache_dir=""
|
||||
cache_dir="$(mtk_cache_dir)"
|
||||
|
||||
if [[ ! -d $cache_dir ]]; then
|
||||
fail "cache dir" "missing: ${cache_dir} - run install.sh"
|
||||
return
|
||||
fi
|
||||
if [[ -w $cache_dir ]]; then
|
||||
pass "cache dir writable" "$cache_dir"
|
||||
else
|
||||
fail "cache dir writable" "not writable: ${cache_dir}"
|
||||
fi
|
||||
}
|
||||
|
||||
main() {
|
||||
printf '\033[1m%s - host readiness for the mtk_root toolkit\033[0m\n' "$SCRIPT_NAME"
|
||||
|
||||
check_tools
|
||||
check_groups
|
||||
check_udev
|
||||
check_venv
|
||||
check_cache
|
||||
|
||||
mtk_heading "Result"
|
||||
printf ' %d passed, %d failed, %d warnings\n\n' "$PASS" "$FAIL" "$WARN"
|
||||
|
||||
if [[ $FAIL -gt 0 ]]; then
|
||||
printf '\033[0;31mNOT READY\033[0m - resolve the failures above (usually: ./install.sh)\n'
|
||||
return 1
|
||||
fi
|
||||
if [[ $WARN -gt 0 ]]; then
|
||||
printf '\033[0;33mREADY, with warnings\033[0m - read them before trusting a USB connection.\n'
|
||||
return 0
|
||||
fi
|
||||
printf '\033[0;32mHOST READY\033[0m - udev access to a real MediaTek device remains unverified\n'
|
||||
printf 'until hardware is attached; that cannot be checked without the phone.\n'
|
||||
return 0
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
-h | --help) usage ;;
|
||||
*)
|
||||
mtk_error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
main
|
||||
@ -1,246 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# 10-recon.sh - Read every fact needed to choose a rooting path. READ-ONLY.
|
||||
#
|
||||
# This script never writes to the device. It issues no reboot, no fastboot
|
||||
# command, no `settings put`, and touches no partition. tests/run_tests.sh
|
||||
# enforces that with a static scan of this file, because the phone it will
|
||||
# most often be pointed at is a daily driver.
|
||||
#
|
||||
# Output: a facts file under the cache dir, plus a decision summary naming
|
||||
# which of the four paths applies and what is still unknown.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||
readonly SCRIPT_DIR
|
||||
readonly SCRIPT_NAME="${0##*/}"
|
||||
|
||||
# shellcheck source=../lib/mtk_common.sh
|
||||
source "$SCRIPT_DIR/../lib/mtk_common.sh"
|
||||
|
||||
# Properties captured verbatim into the facts file. Grouped by what they tell
|
||||
# us; all are read-only.
|
||||
readonly -a FACT_PROPS=(
|
||||
ro.product.manufacturer
|
||||
ro.product.model
|
||||
ro.product.name
|
||||
ro.product.device
|
||||
ro.product.first_api_level
|
||||
ro.board.platform
|
||||
ro.hardware
|
||||
ro.build.fingerprint
|
||||
ro.build.id
|
||||
ro.build.version.release
|
||||
ro.build.version.sdk
|
||||
ro.build.version.security_patch
|
||||
ro.build.type
|
||||
ro.boot.slot_suffix
|
||||
ro.boot.flash.locked
|
||||
ro.boot.verifiedbootstate
|
||||
ro.boot.vbmeta.device_state
|
||||
ro.boot.veritymode
|
||||
ro.boot.hardware
|
||||
ro.boot.bootloader
|
||||
ro.bootloader
|
||||
ro.oem_unlock_supported
|
||||
sys.oem_unlock_allowed
|
||||
ro.carrier
|
||||
gsm.sim.operator.alpha
|
||||
ro.crypto.state
|
||||
)
|
||||
|
||||
REQUESTED_SERIAL=""
|
||||
EXPLAIN_ONLY=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $SCRIPT_NAME [--serial <serial>] [--explain-classification] [--help]
|
||||
|
||||
Reads device facts read-only and prints a path decision summary.
|
||||
Writes a facts file to \$(mtk_cache_dir)/device-facts-<class>-<serial>-<ts>.txt
|
||||
|
||||
--serial <serial> Target a specific device (required if >1 attached)
|
||||
--explain-classification Show why the device classified as it did, then exit
|
||||
--help This text
|
||||
|
||||
Environment:
|
||||
MTK_ROOT_FIXTURE=<dir> Read from fixture files instead of a device (tests)
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Unlock / lock signal interpretation
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
# carrier_lock_verdict
|
||||
#
|
||||
# Deliberately hardcoded to UNDETERMINED. There is no read-only property that
|
||||
# distinguishes "carrier-locked" from "the OEM unlocking toggle is simply off":
|
||||
# - sys.oem_unlock_allowed mirrors the toggle, so it reads empty/0 in both cases
|
||||
# - ro.oem_unlock_supported is not populated on Pixel devices at all
|
||||
# - settings get global oem_unlock_allowed is null until the toggle is used
|
||||
# Printing a negative here would wrongly rule out a whole path, so the script
|
||||
# reports the raw signals and refuses to draw the conclusion.
|
||||
carrier_lock_verdict() {
|
||||
printf 'UNDETERMINED'
|
||||
}
|
||||
|
||||
read_unlock_signals() {
|
||||
UNLOCK_FLASH_LOCKED="$(dev_getprop ro.boot.flash.locked)"
|
||||
UNLOCK_VBSTATE="$(dev_getprop ro.boot.verifiedbootstate)"
|
||||
UNLOCK_DEVICE_STATE="$(dev_getprop ro.boot.vbmeta.device_state)"
|
||||
UNLOCK_SYS_ALLOWED="$(dev_getprop sys.oem_unlock_allowed)"
|
||||
UNLOCK_OEM_SUPPORTED="$(dev_getprop ro.oem_unlock_supported)"
|
||||
|
||||
UNLOCK_SETTING=""
|
||||
if [[ -z ${MTK_ROOT_FIXTURE:-} ]]; then
|
||||
UNLOCK_SETTING="$(mtk_adb shell settings get global oem_unlock_allowed 2>/dev/null | tr -d '\r' || true)"
|
||||
fi
|
||||
|
||||
# The toggle's own state is the thing that matters for unlockability, and it
|
||||
# is precisely the thing that cannot be read reliably. Track it as tri-state.
|
||||
case "$UNLOCK_SYS_ALLOWED" in
|
||||
1) UNLOCK_TOGGLE="ON" ;;
|
||||
0) UNLOCK_TOGGLE="OFF" ;;
|
||||
*) UNLOCK_TOGGLE="UNREADABLE" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Facts file
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
write_facts_file() {
|
||||
local out_path="$1"
|
||||
local tmp_file="" prop="" value=""
|
||||
|
||||
tmp_file="$(mktemp)"
|
||||
# shellcheck disable=SC2064 # expand tmp_file now; it must survive this scope
|
||||
trap "rm -f '$tmp_file'" RETURN
|
||||
|
||||
{
|
||||
printf '# mtk_root device facts\n'
|
||||
printf '# generated: %s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
printf '# serial: %s\n' "$MTK_SERIAL"
|
||||
printf '# class: %s\n' "$MTK_DEVICE_CLASS"
|
||||
printf '#\n'
|
||||
printf '# This file is read by 20-dump-stock.sh, and is the source for the\n'
|
||||
printf '# tests/fixtures/ entries. Format is deliberately line-oriented.\n'
|
||||
printf '\n[properties]\n'
|
||||
for prop in "${FACT_PROPS[@]}"; do
|
||||
value="$(dev_getprop "$prop")"
|
||||
printf '%s=%s\n' "$prop" "$value"
|
||||
done
|
||||
|
||||
printf '\n[settings]\n'
|
||||
printf 'global.oem_unlock_allowed=%s\n' "${UNLOCK_SETTING:-}"
|
||||
|
||||
printf '\n[partitions]\n'
|
||||
printf 'ab_scheme=%s\n' "$MTK_AB_SCHEME"
|
||||
printf 'slot_suffix=%s\n' "${MTK_SLOT_SUFFIX:-}"
|
||||
printf 'slot_suffix_assumed=%s\n' "${MTK_SLOT_ASSUMED:-0}"
|
||||
printf 'ramdisk_partition=%s\n' "${MTK_RAMDISK_PART:-}"
|
||||
printf 'ramdisk_carrier=%s\n' "$(mtk_ramdisk_carrier)"
|
||||
printf 'vbmeta_partition=%s\n' "${MTK_VBMETA_PART:-}"
|
||||
|
||||
printf '\n[by-name]\n'
|
||||
printf '%s\n' "$MTK_BY_NAME"
|
||||
} >"$tmp_file"
|
||||
|
||||
mv "$tmp_file" "$out_path"
|
||||
trap - RETURN
|
||||
printf '%s' "$out_path"
|
||||
}
|
||||
|
||||
# shellcheck source=lib/recon_report.sh
|
||||
source "$SCRIPT_DIR/lib/recon_report.sh"
|
||||
|
||||
main() {
|
||||
printf '\033[1m%s - read-only device recon\033[0m\n' "$SCRIPT_NAME"
|
||||
|
||||
if ! mtk_select_device "$REQUESTED_SERIAL"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Before any getprop: an unauthorized device answers every property query
|
||||
# with an empty string, which is indistinguishable from "device has no such
|
||||
# property" and would produce a confidently wrong report.
|
||||
if ! mtk_require_authorized; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mtk_info "Target: ${MTK_SERIAL}"
|
||||
|
||||
classify_device
|
||||
|
||||
if [[ $EXPLAIN_ONLY -eq 1 ]]; then
|
||||
mtk_explain_classification
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mtk_heading "Identity"
|
||||
printf ' serial : %s\n' "$MTK_SERIAL"
|
||||
printf ' manufacturer : %s\n' "${MTK_PROP_MANUFACTURER:-<unset>}"
|
||||
printf ' model : %s\n' "${MTK_PROP_MODEL:-<unset>}"
|
||||
printf ' platform : %s / %s\n' "${MTK_PROP_PLATFORM:-<unset>}" "${MTK_PROP_HARDWARE:-<unset>}"
|
||||
printf ' classified as : %s\n' "$MTK_DEVICE_CLASS"
|
||||
printf ' because : %s\n' "$MTK_CLASS_REASON"
|
||||
|
||||
if [[ $MTK_DEVICE_CLASS == "UNKNOWN" ]]; then
|
||||
mtk_error "Device did not match any known profile - refusing to report a path."
|
||||
report_decision
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! discover_partitions; then
|
||||
mtk_error "Could not read /dev/block/by-name/ - no partition facts available."
|
||||
mtk_error "On most devices that listing needs no root; if it is empty, the"
|
||||
mtk_error "device may be in an unusual state. Do not flash anything."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
read_unlock_signals
|
||||
|
||||
report_partitions
|
||||
report_lock_state
|
||||
report_manual_checks
|
||||
|
||||
local cache_dir="" facts_path=""
|
||||
cache_dir="$(mtk_cache_dir)"
|
||||
mkdir -p "$cache_dir"
|
||||
facts_path="${cache_dir}/device-facts-${MTK_DEVICE_CLASS}-${MTK_SERIAL}-$(date -u '+%Y%m%dT%H%M%SZ').txt"
|
||||
write_facts_file "$facts_path" >/dev/null
|
||||
|
||||
report_decision
|
||||
|
||||
mtk_heading "Facts file"
|
||||
printf ' %s\n' "$facts_path"
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--serial)
|
||||
[[ $# -ge 2 ]] || {
|
||||
mtk_error "--serial needs a value"
|
||||
exit 1
|
||||
}
|
||||
REQUESTED_SERIAL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--explain-classification)
|
||||
EXPLAIN_ONLY=1
|
||||
shift
|
||||
;;
|
||||
-h | --help) usage ;;
|
||||
*)
|
||||
mtk_error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
main
|
||||
@ -1,242 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# 20-dump-stock.sh - Dump the stock ramdisk + vbmeta off a MediaTek device.
|
||||
#
|
||||
# This is the reason the Ulefone firmware is not pre-downloaded: the stock
|
||||
# images come off the device itself, so they match this exact unit and build.
|
||||
#
|
||||
# Reading requires mtkclient and BROM/preloader mode - the phone must be
|
||||
# powered OFF and plugged in while mtkclient waits. Reading a partition does
|
||||
# not modify it, but this is the first script here that talks to the bootrom,
|
||||
# so it refuses loudly rather than proceeding on a guess.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||
readonly SCRIPT_DIR
|
||||
readonly SCRIPT_NAME="${0##*/}"
|
||||
|
||||
# shellcheck source=../lib/mtk_common.sh
|
||||
source "$SCRIPT_DIR/../lib/mtk_common.sh"
|
||||
|
||||
REQUESTED_SERIAL=""
|
||||
ASSUME_YES=0
|
||||
FORCE=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $SCRIPT_NAME [--serial <serial>] [--yes] [--force] [--help]
|
||||
|
||||
Dumps the stock ramdisk-carrying partition and vbmeta from a MediaTek device
|
||||
using mtkclient, then records sizes and sha256 sums.
|
||||
|
||||
--serial <serial> Target device (required if more than one is attached)
|
||||
--yes Skip the confirmation prompt
|
||||
--force Re-dump even if a complete dump already exists
|
||||
--help This text
|
||||
|
||||
Refuses unless the device is positively identified as MediaTek. Never runs
|
||||
against a Pixel or an unclassified device.
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
# find_latest_facts <serial>
|
||||
# The ramdisk partition name is read from the recon facts file rather than
|
||||
# re-derived here. One source of truth, and it guarantees a dump can only ever
|
||||
# target a partition that a human has already seen in a recon report.
|
||||
find_latest_facts() {
|
||||
local serial="$1" cache_dir=""
|
||||
cache_dir="$(mtk_cache_dir)"
|
||||
|
||||
# A missing cache dir is an ordinary "no recon has run yet", not an error.
|
||||
# Without the guard, `find` on a nonexistent path fails under `set -e` and
|
||||
# the script exits silently before it can explain what to do instead.
|
||||
[[ -d $cache_dir ]] || return 0
|
||||
|
||||
find "$cache_dir" -maxdepth 1 -name "device-facts-*-${serial}-*.txt" 2>/dev/null |
|
||||
sort | tail -n 1
|
||||
}
|
||||
|
||||
read_facts_value() {
|
||||
local facts="$1" key="$2" line=""
|
||||
line="$(grep -m1 -- "^${key}=" "$facts" 2>/dev/null || true)"
|
||||
printf '%s' "${line#*=}"
|
||||
}
|
||||
|
||||
# shellcheck source=lib/dump_partitions.sh
|
||||
source "$SCRIPT_DIR/lib/dump_partitions.sh"
|
||||
# shellcheck source=lib/dump_verify.sh
|
||||
source "$SCRIPT_DIR/lib/dump_verify.sh"
|
||||
|
||||
main() {
|
||||
printf '\033[1m%s - stock partition dump (MediaTek only)\033[0m\n' "$SCRIPT_NAME"
|
||||
|
||||
if ! mtk_select_device "$REQUESTED_SERIAL"; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z ${MTK_ROOT_FIXTURE:-} ]] && ! mtk_require_authorized; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
classify_device
|
||||
|
||||
# Positive assertion, in two independent parts. Deliberately never phrased
|
||||
# as "not a Pixel": an unrecognised third device must fail here too.
|
||||
if [[ $MTK_DEVICE_CLASS != "ULEFONE" ]]; then
|
||||
mtk_error "Device classified as ${MTK_DEVICE_CLASS}; refusing to dump."
|
||||
mtk_error "Reason: ${MTK_CLASS_REASON}"
|
||||
mtk_error "This script only ever runs against a MediaTek Ulefone target."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! mtk_assert_mediatek; then
|
||||
mtk_error "No MediaTek platform signal on this device; refusing to dump."
|
||||
mtk_error "platform='${MTK_PROP_PLATFORM:-}' hardware='${MTK_PROP_HARDWARE:-}'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local facts=""
|
||||
facts="$(find_latest_facts "$MTK_SERIAL")"
|
||||
if [[ -z $facts ]]; then
|
||||
mtk_error "No recon facts file found for ${MTK_SERIAL}."
|
||||
mtk_error "Run ./10-recon.sh --serial ${MTK_SERIAL} first - this script"
|
||||
mtk_error "reads the partition names from that report rather than guessing."
|
||||
exit 1
|
||||
fi
|
||||
mtk_info "Using facts: ${facts}"
|
||||
|
||||
local ramdisk_part="" vbmeta_part="" carrier=""
|
||||
ramdisk_part="$(read_facts_value "$facts" ramdisk_partition)"
|
||||
vbmeta_part="$(read_facts_value "$facts" vbmeta_partition)"
|
||||
carrier="$(read_facts_value "$facts" ramdisk_carrier)"
|
||||
|
||||
if [[ -z $ramdisk_part ]]; then
|
||||
mtk_error "Facts file records no ramdisk partition. Re-run 10-recon.sh."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Skipped when the dump command is overridden, since mtkclient is then not
|
||||
# the thing doing the reading.
|
||||
if [[ -z ${MTK_DUMP_CMD:-} ]]; then
|
||||
local venv_status=""
|
||||
if ! venv_status="$(mtk_check_venv)"; then
|
||||
mtk_error "mtkclient venv unusable: ${venv_status}"
|
||||
mtk_error "Run ./install.sh, then ./00-preflight.sh."
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
local out_dir=""
|
||||
out_dir="$(mtk_stock_dir)/${MTK_SERIAL}"
|
||||
|
||||
if [[ -f "${out_dir}/manifest.txt" && $FORCE -eq 0 ]]; then
|
||||
mtk_info "A dump already exists at ${out_dir}"
|
||||
mtk_info "Re-run with --force to replace it. Existing manifest:"
|
||||
cat "${out_dir}/manifest.txt"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
mtk_heading "About to dump"
|
||||
printf ' device : %s (%s)\n' "$MTK_SERIAL" "${MTK_PROP_MODEL:-}"
|
||||
printf ' ramdisk : %s (carrier: %s)\n' "$ramdisk_part" "${carrier:-unknown}"
|
||||
printf ' vbmeta : %s\n' "${vbmeta_part:-<none found>}"
|
||||
printf ' output : %s\n' "$out_dir"
|
||||
printf '\n Reading is non-destructive, but it needs BROM/preloader mode.\n'
|
||||
|
||||
if [[ $ASSUME_YES -eq 0 ]]; then
|
||||
local reply=""
|
||||
read -r -p " Proceed? [y/N] " reply
|
||||
if [[ ${reply,,} != "y" ]]; then
|
||||
mtk_info "Aborted by user."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$out_dir"
|
||||
|
||||
local failures=0
|
||||
local ramdisk_file="${out_dir}/${ramdisk_part}.img"
|
||||
if dump_partition "$ramdisk_part" "$ramdisk_file"; then
|
||||
verify_dump "$ramdisk_part" "$ramdisk_file" || failures=$((failures + 1))
|
||||
else
|
||||
mtk_error "mtkclient failed to read ${ramdisk_part}"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
|
||||
if [[ -n $vbmeta_part ]]; then
|
||||
local vbmeta_file="${out_dir}/${vbmeta_part}.img"
|
||||
if dump_partition "$vbmeta_part" "$vbmeta_file"; then
|
||||
# vbmeta is legitimately far smaller than a ramdisk carrier, so the
|
||||
# ramdisk size band does not apply; record it without a band check.
|
||||
if [[ -f $vbmeta_file ]]; then
|
||||
mtk_info "${vbmeta_part}: $(mtk_file_size "$vbmeta_file") bytes, sha256 $(mtk_sha256 "$vbmeta_file")"
|
||||
else
|
||||
mtk_error "${vbmeta_part}: no file produced"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
else
|
||||
mtk_error "mtkclient failed to read ${vbmeta_part}"
|
||||
failures=$((failures + 1))
|
||||
fi
|
||||
fi
|
||||
|
||||
# Manifest is plain text on purpose: it is the only artifact here that may
|
||||
# be committed. The images themselves stay in the cache dir.
|
||||
{
|
||||
printf '# mtk_root stock dump manifest\n'
|
||||
printf 'generated=%s\n' "$(date -u '+%Y-%m-%dT%H:%M:%SZ')"
|
||||
printf 'serial=%s\n' "$MTK_SERIAL"
|
||||
printf 'model=%s\n' "${MTK_PROP_MODEL:-}"
|
||||
printf 'fingerprint=%s\n' "$(read_facts_value "$facts" ro.build.fingerprint)"
|
||||
printf 'ramdisk_carrier=%s\n' "${carrier:-}"
|
||||
printf '\n'
|
||||
local f=""
|
||||
for f in "$out_dir"/*.img; do
|
||||
[[ -f $f ]] || continue
|
||||
printf '%s %s %s bytes\n' "$(mtk_sha256 "$f")" "${f##*/}" "$(mtk_file_size "$f")"
|
||||
done
|
||||
} >"${out_dir}/manifest.txt"
|
||||
|
||||
mtk_heading "Result"
|
||||
cat "${out_dir}/manifest.txt"
|
||||
|
||||
if [[ $failures -gt 0 ]]; then
|
||||
mtk_error "${failures} problem(s) above. Do NOT treat this as a usable backup."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mtk_info "Stock images saved to ${out_dir}"
|
||||
mtk_info "Back these up off this machine before flashing anything."
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--serial)
|
||||
[[ $# -ge 2 ]] || {
|
||||
mtk_error "--serial needs a value"
|
||||
exit 1
|
||||
}
|
||||
REQUESTED_SERIAL="$2"
|
||||
shift 2
|
||||
;;
|
||||
--yes)
|
||||
ASSUME_YES=1
|
||||
shift
|
||||
;;
|
||||
--force)
|
||||
FORCE=1
|
||||
shift
|
||||
;;
|
||||
-h | --help) usage ;;
|
||||
*)
|
||||
mtk_error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
main
|
||||
@ -1,192 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# 90-verify-root.sh - Post-flash checks, reported one by one.
|
||||
#
|
||||
# There is deliberately NO aggregate "success" verdict. Root can be partially
|
||||
# working in ways that matter: su present but Magisk's daemon absent, or the
|
||||
# ramdisk flashed while vbmeta verification still rejects it. A single
|
||||
# pass/fail hides exactly the case worth knowing about.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||
readonly SCRIPT_DIR
|
||||
readonly SCRIPT_NAME="${0##*/}"
|
||||
|
||||
# shellcheck source=../lib/mtk_common.sh
|
||||
source "$SCRIPT_DIR/../lib/mtk_common.sh"
|
||||
|
||||
# Known-current Magisk at the time of writing. Checked so a stale APK is
|
||||
# noticed; a mismatch is informational, not a failure.
|
||||
readonly MAGISK_KNOWN_VERSION="30.7"
|
||||
|
||||
REQUESTED_SERIAL=""
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
INFO=0
|
||||
|
||||
check_pass() {
|
||||
PASS=$((PASS + 1))
|
||||
printf ' \033[0;32m[PASS]\033[0m %-28s %s\n' "$1" "${2:-}"
|
||||
}
|
||||
|
||||
check_fail() {
|
||||
FAIL=$((FAIL + 1))
|
||||
printf ' \033[0;31m[FAIL]\033[0m %-28s %s\n' "$1" "${2:-}"
|
||||
}
|
||||
|
||||
check_info() {
|
||||
INFO=$((INFO + 1))
|
||||
printf ' \033[0;34m[INFO]\033[0m %-28s %s\n' "$1" "${2:-}"
|
||||
}
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $SCRIPT_NAME [--serial <serial>] [--help]
|
||||
|
||||
Runs post-flash root checks and reports each independently.
|
||||
Read-only: queries the device, changes nothing.
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
check_su() {
|
||||
local result=""
|
||||
result="$(mtk_adb shell 'su -c id' 2>/dev/null | tr -d '\r' || true)"
|
||||
|
||||
if [[ $result == *"uid=0"* ]]; then
|
||||
check_pass "su grants uid=0" "$result"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# An unanswered Superuser prompt looks identical to no root at all.
|
||||
check_fail "su grants uid=0" "got: ${result:-<no output>}"
|
||||
printf ' If a Superuser prompt is waiting on the phone, grant it and re-run.\n'
|
||||
return 1
|
||||
}
|
||||
|
||||
check_magisk() {
|
||||
local version="" version_code=""
|
||||
version="$(mtk_adb shell 'su -c "magisk -c"' 2>/dev/null | tr -d '\r' || true)"
|
||||
version_code="$(mtk_adb shell 'su -c "magisk -V"' 2>/dev/null | tr -d '\r' || true)"
|
||||
|
||||
if [[ -z $version && -z $version_code ]]; then
|
||||
check_fail "magisk binary responds" "no output from 'magisk -c'"
|
||||
return 1
|
||||
fi
|
||||
check_pass "magisk binary responds" "${version} (code ${version_code})"
|
||||
|
||||
if [[ $version == *"$MAGISK_KNOWN_VERSION"* ]]; then
|
||||
check_info "magisk version" "matches expected v${MAGISK_KNOWN_VERSION}"
|
||||
else
|
||||
check_info "magisk version" "device has '${version}', toolkit expected v${MAGISK_KNOWN_VERSION}"
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
check_daemon() {
|
||||
local result=""
|
||||
result="$(mtk_adb shell 'su -c "magisk --ping"' 2>/dev/null | tr -d '\r' || true)"
|
||||
|
||||
if [[ $result == "Pong" || $result == "pong" ]]; then
|
||||
check_pass "magisk daemon running" "$result"
|
||||
return 0
|
||||
fi
|
||||
check_fail "magisk daemon running" "ping returned: ${result:-<nothing>}"
|
||||
return 1
|
||||
}
|
||||
|
||||
check_boot_state() {
|
||||
local vbstate="" locked=""
|
||||
vbstate="$(dev_getprop ro.boot.verifiedbootstate)"
|
||||
locked="$(dev_getprop ro.boot.flash.locked)"
|
||||
|
||||
# After unlocking, verified boot should no longer report green. Still-green
|
||||
# alongside a claim of successful flashing means the flash did not take.
|
||||
case "$vbstate" in
|
||||
orange | yellow)
|
||||
check_pass "verified boot state" "${vbstate} (expected after unlock)"
|
||||
;;
|
||||
green)
|
||||
check_fail "verified boot state" "green - bootloader still locked, flash did not take"
|
||||
;;
|
||||
*)
|
||||
check_info "verified boot state" "${vbstate:-<unset>}"
|
||||
;;
|
||||
esac
|
||||
|
||||
if [[ $locked == "0" ]]; then
|
||||
check_pass "bootloader unlocked" "ro.boot.flash.locked=0"
|
||||
elif [[ $locked == "1" ]]; then
|
||||
check_fail "bootloader unlocked" "ro.boot.flash.locked=1 - still locked"
|
||||
else
|
||||
check_info "bootloader unlocked" "ro.boot.flash.locked=${locked:-<unset>}"
|
||||
fi
|
||||
}
|
||||
|
||||
check_ramdisk_changed() {
|
||||
local manifest="" carrier=""
|
||||
manifest="$(mtk_stock_dir)/${MTK_SERIAL}/manifest.txt"
|
||||
|
||||
if [[ ! -f $manifest ]]; then
|
||||
check_info "stock manifest" "none at ${manifest} - cannot compare against stock"
|
||||
return 0
|
||||
fi
|
||||
|
||||
carrier="$(grep -m1 '^ramdisk_carrier=' "$manifest" 2>/dev/null || true)"
|
||||
check_info "stock manifest" "present (${carrier#*=}) - compare hashes manually before relocking"
|
||||
}
|
||||
|
||||
main() {
|
||||
printf '\033[1m%s - post-flash verification\033[0m\n' "$SCRIPT_NAME"
|
||||
|
||||
if ! mtk_select_device "$REQUESTED_SERIAL"; then
|
||||
exit 1
|
||||
fi
|
||||
if ! mtk_require_authorized; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
classify_device
|
||||
mtk_info "Target: ${MTK_SERIAL} (${MTK_DEVICE_CLASS})"
|
||||
|
||||
mtk_heading "Checks"
|
||||
# Each runs regardless of earlier failures - a partial result is the point.
|
||||
check_su || true
|
||||
check_magisk || true
|
||||
check_daemon || true
|
||||
check_boot_state
|
||||
check_ramdisk_changed
|
||||
|
||||
mtk_heading "Result"
|
||||
printf ' %d passed, %d failed, %d informational\n\n' "$PASS" "$FAIL" "$INFO"
|
||||
printf ' Read the checks individually. There is no combined verdict here on\n'
|
||||
printf ' purpose: partial root is a real state and it matters which part failed.\n\n'
|
||||
printf ' Play Integrity / SafetyNet is deliberately NOT checked - it needs\n'
|
||||
printf ' Zygisk plus a hiding config, and is a separate exercise.\n'
|
||||
|
||||
[[ $FAIL -eq 0 ]]
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--serial)
|
||||
[[ $# -ge 2 ]] || {
|
||||
mtk_error "--serial needs a value"
|
||||
exit 1
|
||||
}
|
||||
REQUESTED_SERIAL="$2"
|
||||
shift 2
|
||||
;;
|
||||
-h | --help) usage ;;
|
||||
*)
|
||||
mtk_error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
main
|
||||
@ -1,107 +0,0 @@
|
||||
# mtk_root — build notes, surprises, and mistakes
|
||||
|
||||
Written 2026-08-04, while preparing for a Ulefone Armor X12 Pro that had not
|
||||
yet arrived.
|
||||
|
||||
## Installed versions (this host, 2026-08-04)
|
||||
|
||||
| Tool | Version |
|
||||
| ----------------- | -------------------------------- |
|
||||
| adb | 1.0.41 (android-tools) |
|
||||
| fastboot | 37.0.0-android-tools |
|
||||
| python3 | 3.14.6 |
|
||||
| shellcheck | present |
|
||||
| shfmt | 3.13.1 |
|
||||
| systemd / udevadm | 261 (`udevadm verify` available) |
|
||||
| mtkclient | rev `0542a87`, 2026-08-02 |
|
||||
| Magisk | v30.7 |
|
||||
|
||||
## Magisk provenance
|
||||
|
||||
Kept here as well as in `~/.cache/mtk-root/stock/README.md`, because the cache
|
||||
is not version-controlled and vanishes with a cleanup.
|
||||
|
||||
| field | value |
|
||||
| ------- | ------------------------------------------------------------------ |
|
||||
| version | v30.7 (released 2026-02-23) |
|
||||
| asset | `Magisk-v30.7.apk` |
|
||||
| sha256 | `e0d32d2123532860f97123d927b1bb86c4e08e6fd8a48bfc6b5bee0afae9ebd5` |
|
||||
| size | 11613864 bytes |
|
||||
|
||||
**Trap:** the same GitHub release also publishes **`app-debug.apk`** (~25 MB),
|
||||
and a naive "first .apk asset" match picks it. That is the debug build, not the
|
||||
one to install. I hit this during the fetch. Match the filename exactly.
|
||||
|
||||
Re-check before use:
|
||||
|
||||
```bash
|
||||
curl -fsSL https://api.github.com/repos/topjohnwu/Magisk/releases/latest \
|
||||
| grep -E '"(tag_name|browser_download_url)"'
|
||||
```
|
||||
|
||||
## Surprises
|
||||
|
||||
**1. The Ulefone is probably the `init_boot` branch, not `boot`.**
|
||||
The Armor X12 Pro launched on Android 13 (Aug 2023). Per AOSP, devices
|
||||
_launching_ on 13+ carry the ramdisk in `init_boot`, so the Ulefone is likely
|
||||
the opposite branch from the Pixel used to validate this toolkit. The Pixel run
|
||||
therefore does **not** exercise the code path the Ulefone will need. Fixtures
|
||||
cover it; hardware has not.
|
||||
|
||||
**2. The mtkclient venv on this host was broken, not missing.**
|
||||
`~/.cache/bl9000-root/mtkclient/venv` existed and looked complete, but every
|
||||
`import usb` failed under Python 3.14 — a Python upgrade had orphaned it. This
|
||||
is why `00-preflight.sh` tests _importability_ rather than directory existence:
|
||||
the failure mode that costs an evening is the one that looks fine on disk.
|
||||
|
||||
**3. `/etc/udev/rules.d/51-android.rules` had a world-writable catch-all.**
|
||||
`ATTR{idVendor}=="*", ATTR{idProduct}=="*", MODE="0666"` grants every local
|
||||
process read/write access to _every_ USB device — storage, security keys, not
|
||||
just phones. Commented out on 2026-08-04 via `install.sh --fix-udev`; backup at
|
||||
`51-android.rules.bak-20260804T183038Z`. adb connectivity was re-verified
|
||||
afterwards. Note `51-pinephone.rules` still sets `MODE="0666"`, but scoped to
|
||||
specific devices rather than a wildcard.
|
||||
|
||||
**4. SIGPIPE killed the preflight script.**
|
||||
`adb version | head -1` under `set -o pipefail` exits 141 when `head` closes the
|
||||
pipe. The script aborted mid-run on its very first execution. Version strings
|
||||
are now captured whole and sliced with parameter expansion.
|
||||
|
||||
**5. A test fix silently punched a hole in the read-only guard.**
|
||||
To stop the static scan tripping on the word "fastboot" inside `printf` text, I
|
||||
first stripped _all_ quoted strings — which also hid
|
||||
`mtk_adb shell "reboot bootloader"` from every check. The guard protecting a
|
||||
daily-driver phone was weaker after the "fix" than before it. Now only the
|
||||
arguments of `printf`/`echo`/`cat` are stripped, and
|
||||
`test_read_only_scan_catches_injection` asserts the scan actually fails on
|
||||
injected commands.
|
||||
|
||||
**6. `20-dump-stock.sh` exited silently when no recon had run.**
|
||||
`find` on a nonexistent cache dir fails under `set -e`, so the script printed
|
||||
its title and stopped with no explanation. Guarded; it now says which command
|
||||
to run first.
|
||||
|
||||
## Assumptions that need checking against the real device
|
||||
|
||||
- **Ulefone property values are guessed.** `ro.product.manufacturer=Ulefone`,
|
||||
`ro.product.model=Armor X12 Pro`, `ro.board.platform=mt6765`. If the unit
|
||||
reports otherwise, `classify_device` returns `UNKNOWN` and everything
|
||||
refuses — safe, but it will look like a bug. Run
|
||||
`./10-recon.sh --explain-classification`, then widen the patterns at the top
|
||||
of `../lib/mtk_common.sh`.
|
||||
- **Path semantics A/B/C/D are inferred.** The companion document defining them
|
||||
was never provided, so `10-recon.sh` prints an assumption banner every run.
|
||||
- **The Ulefone USB vendor ID is unknown.** `udev/60-mtk-root.rules` covers
|
||||
MediaTek `0e8d`; if the device enumerates under a Ulefone-specific VID, add
|
||||
it (`lsusb` with the phone attached).
|
||||
- **mtkclient's install steps may drift.** `install.sh` falls back to
|
||||
`pip install <clone>` when `requirements.txt` is absent, but read the current
|
||||
README if the venv build ever fails.
|
||||
|
||||
## Verified good news
|
||||
|
||||
mtkclient rev `0542a87` has an explicit **MT6765** entry in
|
||||
`mtkclient/config/brom_config.py` (`name="MT6765/MT8768t"`, loader
|
||||
`mt6765_payload.bin`) — the Ulefone's exact chipset is supported. This does
|
||||
**not** establish that this particular unit's BROM is accessible: SLA/DAA
|
||||
authentication state is per-device and undocumented.
|
||||
@ -1,96 +0,0 @@
|
||||
# mtk_root
|
||||
|
||||
Host tooling for rooting a MediaTek Android device: preflight, read-only
|
||||
recon, stock partition dump, and post-flash verification.
|
||||
|
||||
Built ahead of a Ulefone Armor X12 Pro (MediaTek Helio G36 / MT6765) arriving,
|
||||
and validated read-only against a Pixel 6a.
|
||||
|
||||
**There is no flashing script, deliberately.** Automating irreversible
|
||||
partition writes would defeat the confirmation steps that catch a wrong
|
||||
partition name or a missing slot suffix. Flashing stays manual.
|
||||
|
||||
## Scripts
|
||||
|
||||
| Script | Device needed | What it does |
|
||||
| ------------------- | ------------- | ------------------------------------------------------------------------------- |
|
||||
| `install.sh` | no | Installs deps, clones mtkclient + builds its venv, installs narrowed udev rules |
|
||||
| `00-preflight.sh` | no | Verifies the host is intact. Run this first when the phone arrives |
|
||||
| `10-recon.sh` | yes | **Read-only.** Reads every fact needed to pick a path, writes a facts file |
|
||||
| `20-dump-stock.sh` | yes | MediaTek only. Dumps the stock ramdisk + vbmeta via mtkclient |
|
||||
| `90-verify-root.sh` | yes | Post-flash checks, reported one by one |
|
||||
|
||||
## Usage
|
||||
|
||||
```bash
|
||||
./install.sh # once, or after a Python upgrade
|
||||
./00-preflight.sh # confirm the host is ready
|
||||
./10-recon.sh # read the device, decide the path
|
||||
./20-dump-stock.sh # Ulefone only, after recon
|
||||
# ... patch with Magisk and flash MANUALLY ...
|
||||
./90-verify-root.sh # check what actually worked
|
||||
```
|
||||
|
||||
Add `--serial <serial>` to any device-facing script when more than one phone
|
||||
is attached; they refuse to guess rather than picking one.
|
||||
|
||||
## Design notes
|
||||
|
||||
**Partition discovery never hardcodes `boot` vs `init_boot`.** It reads
|
||||
`/dev/block/by-name/`, derives the slot scheme from observed `_a` suffixes, and
|
||||
picks the ramdisk carrier by preference order `init_boot` → `boot` over what is
|
||||
actually present. Per AOSP, devices launching on Android 13+ carry the ramdisk
|
||||
in `init_boot`; earlier devices keep it in `boot` even after upgrading. That
|
||||
rule explains why the preference order is right, but the code consults the
|
||||
filesystem, not the API level — so one code path serves both a Pixel (no
|
||||
`init_boot`, falls through to `boot_a`) and a 13-launch MediaTek device
|
||||
(`init_boot_a` taken). Patching the wrong one produces a phone that will not
|
||||
boot.
|
||||
|
||||
**Classification refuses rather than guesses.** `ULEFONE` requires a positive
|
||||
MediaTek platform signal _and_ a vendor/model match. There is no "not a Pixel,
|
||||
so presumably the Ulefone" fallback: a third phone attached by accident lands
|
||||
in `UNKNOWN` and every caller stops.
|
||||
|
||||
**Carrier lock is reported as `UNDETERMINED`, always.** No readable property
|
||||
distinguishes carrier-locked from "the OEM unlocking toggle is simply off":
|
||||
`sys.oem_unlock_allowed` mirrors the toggle, `ro.oem_unlock_supported` is not
|
||||
populated on Pixels, and `settings get global oem_unlock_allowed` is null until
|
||||
the toggle is used. Asserting a negative here would wrongly rule out a path, so
|
||||
the script reports the raw signals and refuses to conclude. Settle it out of
|
||||
band via an IMEI check.
|
||||
|
||||
**`10-recon.sh` is read-only and the tests enforce it.** `tests/run_tests.sh`
|
||||
statically scans it for `fastboot`, `magiskboot`, `adb reboot/push/install`,
|
||||
`settings put`, `dd`, `mkfs` and `erase`, and separately asserts the scan
|
||||
_fails_ when such a command is injected into a copy — a guard never observed
|
||||
failing is not known to work.
|
||||
|
||||
## Artifacts
|
||||
|
||||
Images, APKs and facts files go to `~/.cache/mtk-root/`, never into the repo:
|
||||
the pre-commit hook rejects all binaries, and a factory image runs to several
|
||||
GB. Only text manifests belong in git.
|
||||
|
||||
Ulefone firmware is deliberately **not** pre-fetched — the stock ramdisk is
|
||||
dumped off the device itself, so it matches that exact unit and build.
|
||||
|
||||
## Tests
|
||||
|
||||
```bash
|
||||
./tests/run_tests.sh
|
||||
```
|
||||
|
||||
58 assertions, fully offline. Fixtures are `props.txt` + `by-name.txt`
|
||||
directories fed in via `MTK_ROOT_FIXTURE`; `MTK_DUMP_CMD` stubs the mtkclient
|
||||
invocation so the dump/manifest logic is exercised without hardware.
|
||||
|
||||
`tests/fixtures/pixel-boot-ab/` is harvested from a real Pixel 6a. The
|
||||
`ulefone-*` fixtures are synthetic and encode _assumed_ property values — see
|
||||
`NOTES.md`.
|
||||
|
||||
## Related
|
||||
|
||||
`../single_use/utils/root_bl9000.sh` is an older, single-device rooting script
|
||||
for a Blackview MT6893. It is untouched by this toolkit and still works; the
|
||||
two share no code by design, so changes here cannot break it.
|
||||
@ -1,123 +0,0 @@
|
||||
# READY — the day the Ulefone arrives
|
||||
|
||||
Prepared 2026-08-04. Everything below was built and tested before the device
|
||||
existed on this desk. Read the status matrix before trusting any of it.
|
||||
|
||||
## Command sequence
|
||||
|
||||
Run from `~/testsAndMisc/linux_configuration/scripts/mtk_root/`.
|
||||
|
||||
```bash
|
||||
# 1. Confirm nothing rotted in transit. Expect "HOST READY".
|
||||
./00-preflight.sh
|
||||
|
||||
# 2. Enable Developer options -> USB debugging on the phone, plug it in,
|
||||
# accept the RSA prompt on its screen, then:
|
||||
./10-recon.sh
|
||||
|
||||
# Read the decision summary. If it says UNKNOWN, the property patterns need
|
||||
# widening -- see below. If it names a path, continue.
|
||||
|
||||
# 3. Check the phone screen yourself:
|
||||
# Settings > System > Developer options > "OEM unlocking"
|
||||
# toggleable => unlocking permitted; greyed out => blocked.
|
||||
# A property cannot tell you this. Only the UI can.
|
||||
|
||||
# 4. Dump the stock images BEFORE touching anything.
|
||||
./20-dump-stock.sh
|
||||
# Then copy ~/.cache/mtk-root/stock/ somewhere off this machine.
|
||||
|
||||
# 5. Patch and flash MANUALLY -- there is no script for this on purpose.
|
||||
# Use the partition name 10-recon.sh reported, not one from memory.
|
||||
|
||||
# 6. Check what actually worked:
|
||||
./90-verify-root.sh
|
||||
```
|
||||
|
||||
## If recon says UNKNOWN
|
||||
|
||||
Expected on first contact: the Ulefone's property values were guessed.
|
||||
|
||||
```bash
|
||||
./10-recon.sh --explain-classification
|
||||
```
|
||||
|
||||
It prints each property and the pattern it failed. Edit the marked block at
|
||||
the top of `../lib/mtk_common.sh`, then re-run. The refusal is deliberate —
|
||||
the alternative is a toolkit that treats any unrecognised phone as the target.
|
||||
|
||||
## Status: what is validated and what is not
|
||||
|
||||
| Capability | Status |
|
||||
| --------------------------------------------------------- | -------------------------------------------------------------------- |
|
||||
| Host tool checks, groups, udev syntax, venv importability | **host-validated** — 12/12 on this machine |
|
||||
| Device enumeration, `unauthorized` vs `device` triage | **device-validated** (Pixel 6a) |
|
||||
| PIXEL classification | **device-validated** |
|
||||
| Partition discovery, **`boot`** carrier, A/B, slot suffix | **device-validated** — real Pixel, 53 partitions |
|
||||
| Facts-file format and contents | **device-validated** |
|
||||
| Partition discovery, **`init_boot`** carrier | **fixture-tested only — never run against hardware** |
|
||||
| A-only slot scheme | **fixture-tested only** |
|
||||
| UNKNOWN-device refusal; non-MediaTek dump refusal | **fixture-tested only** |
|
||||
| Dump manifest, sha256, size sanity, idempotence | **fixture-tested only** — mtkclient stubbed via `MTK_DUMP_CMD` |
|
||||
| `10-recon.sh` is read-only | **statically enforced**, with a negative self-test |
|
||||
| Carrier-lock determination | **not possible from the device — reported `UNDETERMINED` by design** |
|
||||
| mtkclient reading a real partition over BROM | **UNTESTED** — needs the Ulefone |
|
||||
| Magisk patch → flash → `90-verify-root.sh` | **UNTESTED** — no unlocked device exists |
|
||||
| Ulefone property values; A/B/C/D path semantics | **GUESSED / ASSUMED** |
|
||||
|
||||
The single most important line: **the Pixel validated the `boot` branch, and
|
||||
the Ulefone almost certainly needs the `init_boot` branch.** The selection
|
||||
logic is proven by fixtures; it has never run against a device that has an
|
||||
`init_boot` partition.
|
||||
|
||||
## Pixel 6a — resolved status
|
||||
|
||||
| | |
|
||||
| ----------------- | -------------------------------------------------------------------- |
|
||||
| serial | `23181JEGR08034` |
|
||||
| build | `bluejay:16/CP1A.260405.005` (Android 16, security patch 2026-04-05) |
|
||||
| bootloader | `bluejay-16.4-14548185` |
|
||||
| launch API level | 32 (Android 12) → ramdisk in **`boot`**, no `init_boot` |
|
||||
| partitions | `boot_a`/`boot_b`, `vbmeta_a`/`vbmeta_b`, 53 total |
|
||||
| bootloader state | locked (`flash.locked=1`, `verifiedbootstate=green`) |
|
||||
| OEM unlock toggle | never enabled (`settings global oem_unlock_allowed` = null) |
|
||||
| **carrier lock** | **UNDETERMINED — needs an out-of-band check** |
|
||||
| path | **C or D, indeterminate** |
|
||||
|
||||
**The carrier-lock question is not answered, and cannot be from software.**
|
||||
`sys.oem_unlock_allowed` and `ro.oem_unlock_supported` are both unset, and
|
||||
`settings get global oem_unlock_allowed` returns null — but those read
|
||||
identically whether the phone is carrier-locked or the toggle has simply never
|
||||
been switched on. `ro.carrier=unknown` suggests a non-carrier build and
|
||||
`gsm.sim.operator.alpha=Orange` is only the current SIM; neither settles it.
|
||||
|
||||
**To settle it:** look at Developer options → OEM unlocking on the phone. If it
|
||||
is toggleable, it is not carrier-locked and Path C is open. If greyed out,
|
||||
check the IMEI with the carrier or against the Google Store order before
|
||||
concluding — a toggle can grey out for no network, an unverified account, or a
|
||||
pending update on a phone that is not carrier-locked at all.
|
||||
|
||||
## Re-check before use — these go stale
|
||||
|
||||
- **Magisk v30.7** (2026-02-23), sha256 `e0d32d2…afae9ebd5`. Verify it is still
|
||||
current; beware `app-debug.apk` in the same release, which is _not_ the one
|
||||
to install. See `NOTES.md`.
|
||||
- **mtkclient rev `0542a87`** (2026-08-02). `./install.sh` pulls and rebuilds.
|
||||
Good sign: this rev has an explicit MT6765 entry with an `mt6765_payload.bin`
|
||||
loader — the Ulefone's exact chipset.
|
||||
- **Pixel factory images** at `developers.google.com/android/images#bluejay`.
|
||||
Not downloaded; the toolkit does not act on the Pixel. Google moved the
|
||||
Pixel 6 series off monthly cadence, so the newest listed build may be ahead
|
||||
of what the phone runs.
|
||||
|
||||
## Open questions that need the physical device
|
||||
|
||||
- The BROM-mode key combination for the X12 Pro
|
||||
- Whether mtkclient enumerates this unit, and whether its BROM is open or
|
||||
SLA/DAA-locked (chipset support ≠ this unit being accessible)
|
||||
- Whether `fastboot flashing unlock` is enabled in its firmware build
|
||||
- Whether the OEM unlocking toggle is available at all
|
||||
- Its exact build ID
|
||||
- Whether the udev rules genuinely grant access to the MediaTek VID in practice
|
||||
— rules syntax is verified, real BROM access is not
|
||||
- Its USB vendor ID, if it differs from MediaTek's `0e8d`
|
||||
@ -1,141 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# install.sh - Set up host tooling for the mtk_root toolkit.
|
||||
#
|
||||
# Idempotent: safe to run repeatedly. Ends by running 00-preflight.sh, because
|
||||
# a tool that was never executed is not installed, only copied.
|
||||
#
|
||||
# Leaves the older ~/.cache/bl9000-root workspace completely alone - a
|
||||
# different script still owns it.
|
||||
# ============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
|
||||
readonly SCRIPT_DIR
|
||||
readonly SCRIPT_NAME="${0##*/}"
|
||||
|
||||
# shellcheck source=../lib/mtk_common.sh
|
||||
source "$SCRIPT_DIR/../lib/mtk_common.sh"
|
||||
|
||||
readonly MTKCLIENT_REPO="https://github.com/bkerler/mtkclient"
|
||||
readonly UDEV_SOURCE="$SCRIPT_DIR/udev/60-mtk-root.rules"
|
||||
readonly UDEV_TARGET="/etc/udev/rules.d/60-mtk-root.rules"
|
||||
readonly -a PACMAN_PACKAGES=(android-tools android-udev git python libusb)
|
||||
|
||||
FIX_UDEV=0
|
||||
SKIP_PACMAN=0
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $SCRIPT_NAME [--fix-udev] [--skip-pacman] [--help]
|
||||
|
||||
Installs host dependencies, clones mtkclient into the cache dir, builds its
|
||||
venv, and installs narrowed udev rules.
|
||||
|
||||
--fix-udev Also comment out any world-writable catch-all udev rule
|
||||
(idVendor=="*" with MODE="0666"), keeping a timestamped
|
||||
backup. Not done by default: another script may rely on it.
|
||||
--skip-pacman Do not install system packages (useful when already present)
|
||||
--help This text
|
||||
EOF
|
||||
exit 0
|
||||
}
|
||||
|
||||
install_packages() {
|
||||
mtk_heading "System packages"
|
||||
|
||||
if [[ $SKIP_PACMAN -eq 1 ]]; then
|
||||
mtk_info "Skipping package installation (--skip-pacman)."
|
||||
return 0
|
||||
fi
|
||||
|
||||
local -a missing=()
|
||||
local pkg=""
|
||||
for pkg in "${PACMAN_PACKAGES[@]}"; do
|
||||
if ! pacman -Qi "$pkg" >/dev/null 2>&1; then
|
||||
missing+=("$pkg")
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ ${#missing[@]} -eq 0 ]]; then
|
||||
mtk_info "All required packages already installed."
|
||||
return 0
|
||||
fi
|
||||
|
||||
mtk_info "Installing: ${missing[*]}"
|
||||
# Interactive by design. A non-interactive sudo pacman can block forever on
|
||||
# the database lock with no visible prompt; better to let it ask.
|
||||
if ! sudo pacman -S --needed "${missing[@]}"; then
|
||||
mtk_error "Package installation failed. Install manually: ${missing[*]}"
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
ensure_groups() {
|
||||
mtk_heading "Groups"
|
||||
|
||||
local group="" changed=0
|
||||
for group in plugdev adbusers; do
|
||||
if ! getent group "$group" >/dev/null 2>&1; then
|
||||
mtk_info "Creating group ${group}"
|
||||
sudo groupadd "$group" || {
|
||||
mtk_warn "Could not create ${group}"
|
||||
continue
|
||||
}
|
||||
fi
|
||||
if id -nG "$USER" | grep -qw "$group"; then
|
||||
mtk_info "Already a member of ${group}"
|
||||
else
|
||||
mtk_info "Adding ${USER} to ${group}"
|
||||
sudo usermod -aG "$group" "$USER" && changed=1
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ $changed -eq 1 ]]; then
|
||||
mtk_warn "Group membership changed - log out and back in before USB access works."
|
||||
fi
|
||||
}
|
||||
|
||||
# shellcheck source=lib/mtk_udev.sh
|
||||
source "$SCRIPT_DIR/lib/mtk_udev.sh"
|
||||
|
||||
main() {
|
||||
printf '\033[1m%s - host setup for the mtk_root toolkit\033[0m\n' "$SCRIPT_NAME"
|
||||
|
||||
install_packages
|
||||
ensure_groups
|
||||
prepare_cache
|
||||
install_udev
|
||||
setup_mtkclient
|
||||
|
||||
mtk_heading "Verifying"
|
||||
if "$SCRIPT_DIR/00-preflight.sh"; then
|
||||
mtk_info "Install complete."
|
||||
else
|
||||
mtk_warn "Preflight reported problems - see above."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--fix-udev)
|
||||
FIX_UDEV=1
|
||||
shift
|
||||
;;
|
||||
--skip-pacman)
|
||||
SKIP_PACMAN=1
|
||||
shift
|
||||
;;
|
||||
-h | --help) usage ;;
|
||||
*)
|
||||
mtk_error "Unknown option: $1"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
main
|
||||
@ -1,39 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Reading facts and dumping individual partitions.
|
||||
#
|
||||
# Sourced by 20-dump-stock.sh; split out to keep it under the 250-line cap.
|
||||
# Sourced rather than run, so it inherits the caller's strict mode and
|
||||
# the helper functions and variables defined above the source line.
|
||||
|
||||
# dump_partition <partition> <destination>
|
||||
#
|
||||
# MTK_DUMP_CMD overrides the mtkclient invocation and is the seam the tests
|
||||
# use: it is called as `$MTK_DUMP_CMD <partition> <destination>`. Without it
|
||||
# the only way to exercise the manifest/verification logic below would be to
|
||||
# attach a phone in BROM mode, so that code would ship unrun.
|
||||
dump_partition() {
|
||||
local partition="$1" dest="$2"
|
||||
local mtk_dir="" python_bin=""
|
||||
|
||||
mtk_info "Dumping ${partition} -> ${dest}"
|
||||
|
||||
if [[ -n ${MTK_DUMP_CMD:-} ]]; then
|
||||
# shellcheck disable=SC2086 # intentional word splitting: the override may
|
||||
# carry arguments, e.g. MTK_DUMP_CMD="./stub.sh --size 4M"
|
||||
${MTK_DUMP_CMD} "$partition" "$dest"
|
||||
return $?
|
||||
fi
|
||||
|
||||
mtk_dir="$(mtk_mtkclient_dir)"
|
||||
python_bin="$(mtk_venv_python)"
|
||||
|
||||
mtk_info "mtkclient is waiting for the device in BROM/preloader mode."
|
||||
mtk_info "Power the phone OFF, then connect USB (some units need vol- held)."
|
||||
|
||||
# `mtk.py r <partition> <outfile>` per the mtkclient README. Run from the
|
||||
# clone directory because mtk.py resolves its Loader/ paths relative to cwd.
|
||||
(
|
||||
cd "$mtk_dir" || exit 1
|
||||
"$python_bin" mtk.py r "$partition" "$dest"
|
||||
)
|
||||
}
|
||||
@ -1,31 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Dump verification and reporting for the stock partition dump.
|
||||
#
|
||||
# Sourced by 20-dump-stock.sh; split out to keep it under the 250-line cap.
|
||||
# Sourced rather than run, so it inherits the caller's strict mode and
|
||||
# the helper functions and variables defined above the source line.
|
||||
|
||||
verify_dump() {
|
||||
local label="$1" path="$2"
|
||||
local size="" verdict="" sha=""
|
||||
|
||||
if [[ ! -f $path ]]; then
|
||||
mtk_error "${label}: no file produced at ${path}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
size="$(mtk_file_size "$path")"
|
||||
sha="$(mtk_sha256 "$path")"
|
||||
|
||||
if verdict="$(mtk_sane_ramdisk_size "$size")"; then
|
||||
mtk_info "${label}: ${verdict}, sha256 ${sha}"
|
||||
else
|
||||
# mtkclient exits 0 on a truncated read, so size is the only signal that
|
||||
# something went wrong short of parsing the image.
|
||||
mtk_warn "${label}: ${verdict}"
|
||||
mtk_warn "${label}: sha256 ${sha}"
|
||||
mtk_warn "Inspect this image before trusting it as a backup."
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
@ -1,129 +0,0 @@
|
||||
#!/bin/bash
|
||||
# udev rules and the mtkclient checkout for MediaTek rooting.
|
||||
#
|
||||
# Sourced by install.sh; split out to keep it under the 250-line cap.
|
||||
# Sourced rather than run, so it inherits the caller's strict mode and
|
||||
# the helper functions and variables defined above the source line.
|
||||
|
||||
install_udev() {
|
||||
mtk_heading "udev rules"
|
||||
|
||||
if [[ ! -f $UDEV_SOURCE ]]; then
|
||||
mtk_error "Missing rules source: ${UDEV_SOURCE}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [[ -f $UDEV_TARGET ]] && sudo cmp -s "$UDEV_SOURCE" "$UDEV_TARGET"; then
|
||||
mtk_info "Rules already current at ${UDEV_TARGET}"
|
||||
else
|
||||
mtk_info "Installing ${UDEV_TARGET}"
|
||||
sudo install -m 0644 "$UDEV_SOURCE" "$UDEV_TARGET"
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
fi
|
||||
|
||||
if command -v udevadm >/dev/null 2>&1 && udevadm verify --help >/dev/null 2>&1; then
|
||||
if udevadm verify "$UDEV_TARGET" >/dev/null 2>&1; then
|
||||
mtk_info "udevadm verify: clean"
|
||||
else
|
||||
mtk_error "udevadm verify rejected ${UDEV_TARGET}"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
handle_catchall
|
||||
}
|
||||
|
||||
handle_catchall() {
|
||||
local catchall=""
|
||||
catchall="$(mtk_udev_catchall_files)"
|
||||
[[ -z $catchall ]] && return 0
|
||||
|
||||
mtk_warn "World-writable catch-all udev rule found in:"
|
||||
printf '%s\n' "$catchall" | sed 's/^/ /'
|
||||
mtk_warn 'A rule matching idVendor=="*" with MODE="0666" grants every local'
|
||||
mtk_warn 'process read/write access to EVERY USB device, not just phones.'
|
||||
|
||||
if [[ $FIX_UDEV -eq 0 ]]; then
|
||||
mtk_warn "Not changing it. Another script may depend on it."
|
||||
mtk_warn "To narrow it: ${SCRIPT_NAME} --fix-udev"
|
||||
return 0
|
||||
fi
|
||||
|
||||
local file="" stamp=""
|
||||
stamp="$(date -u '+%Y%m%dT%H%M%SZ')"
|
||||
while IFS= read -r file; do
|
||||
[[ -n $file ]] || continue
|
||||
mtk_info "Backing up ${file} -> ${file}.bak-${stamp}"
|
||||
sudo cp -a "$file" "${file}.bak-${stamp}"
|
||||
# Comment the offending line rather than deleting it, so the change is
|
||||
# obvious and trivially reversible.
|
||||
sudo sed -i -E 's|^([^#].*idVendor\}=="\*".*MODE="0666".*)$|# disabled by mtk_root install.sh --fix-udev: world-writable catch-all\n#\1|' "$file"
|
||||
mtk_info "Commented catch-all in ${file}"
|
||||
done <<<"$catchall"
|
||||
|
||||
sudo udevadm control --reload-rules
|
||||
sudo udevadm trigger
|
||||
}
|
||||
|
||||
setup_mtkclient() {
|
||||
mtk_heading "mtkclient"
|
||||
|
||||
local clone_dir=""
|
||||
clone_dir="$(mtk_mtkclient_dir)"
|
||||
|
||||
if [[ -d "$clone_dir/.git" ]]; then
|
||||
mtk_info "Updating existing clone at ${clone_dir}"
|
||||
git -C "$clone_dir" pull --ff-only || mtk_warn "Pull failed; continuing with current checkout."
|
||||
else
|
||||
mtk_info "Cloning ${MTKCLIENT_REPO}"
|
||||
mkdir -p "$(dirname "$clone_dir")"
|
||||
git clone --depth 1 "$MTKCLIENT_REPO" "$clone_dir"
|
||||
fi
|
||||
|
||||
local rev=""
|
||||
rev="$(git -C "$clone_dir" rev-parse --short HEAD 2>/dev/null || echo unknown)"
|
||||
mtk_info "mtkclient at rev ${rev}"
|
||||
|
||||
# Rebuild the venv when it cannot import its dependencies. A Python upgrade
|
||||
# orphans a venv while leaving it looking complete on disk - the venv found
|
||||
# on this host in Aug 2026 failed exactly that way.
|
||||
if ! mtk_check_venv >/dev/null 2>&1; then
|
||||
if [[ -d "$clone_dir/venv" ]]; then
|
||||
mtk_warn "Existing venv is unusable; rebuilding."
|
||||
rm -rf "$clone_dir/venv"
|
||||
fi
|
||||
mtk_info "Creating venv with $(python3 -V 2>&1)"
|
||||
python3 -m venv "$clone_dir/venv"
|
||||
"$clone_dir/venv/bin/pip" install --upgrade pip
|
||||
|
||||
if [[ -f "$clone_dir/requirements.txt" ]]; then
|
||||
"$clone_dir/venv/bin/pip" install -r "$clone_dir/requirements.txt"
|
||||
else
|
||||
# Newer mtkclient revisions ship a pyproject instead of requirements.txt.
|
||||
mtk_info "No requirements.txt; installing the package itself."
|
||||
"$clone_dir/venv/bin/pip" install "$clone_dir"
|
||||
fi
|
||||
else
|
||||
mtk_info "venv already usable."
|
||||
fi
|
||||
|
||||
local status=""
|
||||
if status="$(mtk_check_venv)"; then
|
||||
mtk_info "venv check: ${status}"
|
||||
else
|
||||
mtk_error "venv still unusable after install: ${status}"
|
||||
mtk_error "Read ${clone_dir}/README.md - its install steps may have changed."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
prepare_cache() {
|
||||
mtk_heading "Workspace"
|
||||
local cache_dir=""
|
||||
cache_dir="$(mtk_cache_dir)"
|
||||
mkdir -p "$cache_dir" "$(mtk_stock_dir)"
|
||||
mtk_info "Cache: ${cache_dir}"
|
||||
mtk_info "Artifacts stay here, outside the repo - it rejects binaries, and"
|
||||
mtk_info "factory images run to several GB."
|
||||
}
|
||||
@ -1,112 +0,0 @@
|
||||
#!/bin/bash
|
||||
# Partition, lock-state and manual-check reporting for MTK recon.
|
||||
#
|
||||
# Sourced by 10-recon.sh; split out to keep it under the 250-line cap.
|
||||
# Sourced rather than run, so it inherits the caller's strict mode and
|
||||
# the helper functions and variables defined above the source line.
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Reporting
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
report_partitions() {
|
||||
local carrier=""
|
||||
carrier="$(mtk_ramdisk_carrier)"
|
||||
|
||||
mtk_heading "Partitions"
|
||||
printf ' A/B scheme : %s\n' "$MTK_AB_SCHEME"
|
||||
if [[ ${MTK_SLOT_ASSUMED:-0} -eq 1 ]]; then
|
||||
printf ' slot suffix : %s (ASSUMED - ro.boot.slot_suffix was empty)\n' "${MTK_SLOT_SUFFIX:-none}"
|
||||
else
|
||||
printf ' slot suffix : %s\n' "${MTK_SLOT_SUFFIX:-none}"
|
||||
fi
|
||||
printf ' ramdisk lives in: %s -> partition %s\n' "${carrier:-UNKNOWN}" "${MTK_RAMDISK_PART:-NONE FOUND}"
|
||||
printf ' vbmeta : %s\n' "${MTK_VBMETA_PART:-NONE FOUND}"
|
||||
|
||||
# boot vs init_boot is the single most consequential fact here: patching the
|
||||
# wrong one produces a device that does not boot.
|
||||
if [[ $carrier == "init_boot" ]]; then
|
||||
printf ' => Magisk must patch INIT_BOOT on this device.\n'
|
||||
elif [[ $carrier == "boot" ]]; then
|
||||
printf ' => Magisk must patch BOOT on this device (no init_boot present).\n'
|
||||
else
|
||||
printf ' => Could not identify a ramdisk carrier. Do not flash anything.\n'
|
||||
fi
|
||||
|
||||
mtk_check_layout_expectation || true
|
||||
}
|
||||
|
||||
report_lock_state() {
|
||||
mtk_heading "Lock state"
|
||||
printf ' ro.boot.flash.locked : %s\n' "${UNLOCK_FLASH_LOCKED:-<unset>}"
|
||||
printf ' ro.boot.verifiedbootstate : %s\n' "${UNLOCK_VBSTATE:-<unset>}"
|
||||
printf ' ro.boot.vbmeta.device_state : %s\n' "${UNLOCK_DEVICE_STATE:-<unset>}"
|
||||
printf ' sys.oem_unlock_allowed : %s\n' "${UNLOCK_SYS_ALLOWED:-<unset>}"
|
||||
printf ' ro.oem_unlock_supported : %s\n' "${UNLOCK_OEM_SUPPORTED:-<unset>}"
|
||||
printf ' settings oem_unlock_allowed : %s\n' "${UNLOCK_SETTING:-<unset>}"
|
||||
printf '\n'
|
||||
printf ' OEM-unlock toggle : %s\n' "$UNLOCK_TOGGLE"
|
||||
printf ' Carrier lock : %s\n' "$(carrier_lock_verdict)"
|
||||
printf '\n'
|
||||
printf ' Carrier lock cannot be determined from any property. An unset\n'
|
||||
printf ' sys.oem_unlock_allowed means the toggle is off, which is NOT the same\n'
|
||||
printf ' as carrier-locked. Settle it out of band: check the IMEI with the\n'
|
||||
printf ' carrier, or in the Google Store order record.\n'
|
||||
}
|
||||
|
||||
report_manual_checks() {
|
||||
mtk_heading "Check these on the phone screen yourself"
|
||||
printf ' Settings > System > Developer options > "OEM unlocking":\n'
|
||||
printf ' - toggleable => bootloader unlock is permitted by the firmware\n'
|
||||
printf ' - greyed out => blocked (carrier policy, no network, or unsupported)\n'
|
||||
printf ' A property can say the toggle is off; only the UI shows whether it\n'
|
||||
printf ' CAN be turned on. That distinction decides the path.\n'
|
||||
}
|
||||
|
||||
report_decision() {
|
||||
mtk_heading "Decision summary"
|
||||
|
||||
# The companion document that defines paths A-D was not provided to the
|
||||
# session that wrote this script, so the mapping below is inferred. Say so
|
||||
# every run rather than let a plausible-looking verdict pass as authoritative.
|
||||
printf ' \033[0;33mPATH SEMANTICS ARE ASSUMED\033[0m - the companion doc defining A/B/C/D was\n'
|
||||
printf ' not available when this was written. Reconcile before acting.\n\n'
|
||||
|
||||
case "$MTK_DEVICE_CLASS" in
|
||||
ULEFONE)
|
||||
if [[ $UNLOCK_TOGGLE == "ON" ]]; then
|
||||
printf ' => PATH A (Ulefone, OEM unlocking available)\n'
|
||||
printf ' Next: ./20-dump-stock.sh --serial %s\n' "$MTK_SERIAL"
|
||||
printf ' Then patch %s with Magisk and flash MANUALLY.\n' "${MTK_RAMDISK_PART:-<unknown>}"
|
||||
else
|
||||
printf ' => PATH A or B (Ulefone; toggle reads %s)\n' "$UNLOCK_TOGGLE"
|
||||
printf ' Confirm the toggle on-screen before choosing.\n'
|
||||
printf ' If greyed out => PATH B: fastboot unlock is blocked, leaving the\n'
|
||||
printf ' mtkclient/BROM route, which is the untested one. Dump first:\n'
|
||||
printf ' ./20-dump-stock.sh --serial %s\n' "$MTK_SERIAL"
|
||||
fi
|
||||
;;
|
||||
PIXEL)
|
||||
printf ' => PATH C or D (Pixel) - INDETERMINATE\n'
|
||||
printf ' The toggle reads %s and carrier lock is UNDETERMINED, so C and D\n' "$UNLOCK_TOGGLE"
|
||||
printf ' cannot be told apart from properties alone.\n\n'
|
||||
printf ' \033[0;31mThis toolkit will not act on a Pixel.\033[0m It is the daily driver;\n'
|
||||
printf ' recon only. No dump, unlock, or flash step here targets it.\n'
|
||||
;;
|
||||
*)
|
||||
printf ' => NO PATH. Device did not classify.\n'
|
||||
printf ' %s\n' "$MTK_CLASS_REASON"
|
||||
printf ' Refusing to guess. Run --explain-classification, and if this is\n'
|
||||
printf ' genuinely the Ulefone, widen the patterns at the top of\n'
|
||||
printf ' lib/mtk_common.sh.\n'
|
||||
;;
|
||||
esac
|
||||
|
||||
mtk_heading "Still unknown"
|
||||
printf ' - Whether the OEM unlocking toggle can be enabled (needs the screen)\n'
|
||||
printf ' - Carrier lock status (needs an out-of-band IMEI check)\n'
|
||||
if [[ $MTK_DEVICE_CLASS == "ULEFONE" ]]; then
|
||||
printf ' - Whether this unit BROM is open or SLA/DAA-locked (needs mtkclient)\n'
|
||||
printf ' - Whether fastboot flashing unlock is enabled in this firmware build\n'
|
||||
fi
|
||||
}
|
||||
@ -1,53 +0,0 @@
|
||||
abl_a
|
||||
abl_b
|
||||
bl1_a
|
||||
bl1_b
|
||||
bl2_a
|
||||
bl2_b
|
||||
bl31_a
|
||||
bl31_b
|
||||
boot_a
|
||||
boot_b
|
||||
devinfo
|
||||
dpm_a
|
||||
dpm_b
|
||||
dram_train_a
|
||||
dram_train_b
|
||||
dtbo_a
|
||||
dtbo_b
|
||||
efs
|
||||
efs_backup
|
||||
fips
|
||||
frp
|
||||
gsa_a
|
||||
gsa_b
|
||||
klog
|
||||
ldfw_a
|
||||
ldfw_b
|
||||
metadata
|
||||
mfg_data
|
||||
misc
|
||||
modem_a
|
||||
modem_b
|
||||
modem_userdata
|
||||
pbl_a
|
||||
pbl_b
|
||||
persist
|
||||
pvmfw_a
|
||||
pvmfw_b
|
||||
sda
|
||||
sdb
|
||||
sdc
|
||||
sdd
|
||||
super
|
||||
tzsw_a
|
||||
tzsw_b
|
||||
userdata
|
||||
vbmeta_a
|
||||
vbmeta_b
|
||||
vbmeta_system_a
|
||||
vbmeta_system_b
|
||||
vbmeta_vendor_a
|
||||
vbmeta_vendor_b
|
||||
vendor_boot_a
|
||||
vendor_boot_b
|
||||
@ -1,24 +0,0 @@
|
||||
ro.product.manufacturer=Google
|
||||
ro.product.model=Pixel 6a
|
||||
ro.product.name=bluejay
|
||||
ro.product.device=bluejay
|
||||
ro.product.first_api_level=32
|
||||
ro.board.platform=gs101
|
||||
ro.hardware=bluejay
|
||||
ro.build.fingerprint=google/bluejay/bluejay:16/CP1A.260405.005/15001963:user/release-keys
|
||||
ro.build.id=CP1A.260405.005
|
||||
ro.build.version.release=16
|
||||
ro.build.version.sdk=36
|
||||
ro.build.version.security_patch=2026-04-05
|
||||
ro.build.type=user
|
||||
ro.boot.slot_suffix=_a
|
||||
ro.boot.flash.locked=1
|
||||
ro.boot.verifiedbootstate=green
|
||||
ro.boot.vbmeta.device_state=locked
|
||||
ro.boot.veritymode=enforcing
|
||||
ro.boot.hardware=bluejay
|
||||
ro.boot.bootloader=bluejay-16.4-14548185
|
||||
ro.bootloader=bluejay-16.4-14548185
|
||||
ro.carrier=unknown
|
||||
gsm.sim.operator.alpha=Orange
|
||||
ro.crypto.state=encrypted
|
||||
@ -1,8 +0,0 @@
|
||||
boot_a
|
||||
boot_b
|
||||
init_boot_a
|
||||
init_boot_b
|
||||
vbmeta_a
|
||||
vbmeta_b
|
||||
super
|
||||
userdata
|
||||
@ -1,7 +0,0 @@
|
||||
ro.product.manufacturer=Ulefone
|
||||
ro.product.model=Armor X12 Pro
|
||||
ro.product.device=Armor_X12_Pro
|
||||
ro.board.platform=mt6765
|
||||
ro.hardware=mt6765
|
||||
ro.product.first_api_level=33
|
||||
ro.boot.slot_suffix=_a
|
||||
@ -1,5 +0,0 @@
|
||||
boot
|
||||
init_boot
|
||||
vbmeta
|
||||
super
|
||||
userdata
|
||||
@ -1,5 +0,0 @@
|
||||
ro.product.manufacturer=Ulefone
|
||||
ro.product.model=Armor X12 Pro
|
||||
ro.board.platform=mt6765
|
||||
ro.hardware=mt6765
|
||||
ro.product.first_api_level=33
|
||||
@ -1,2 +0,0 @@
|
||||
boot_a
|
||||
vbmeta_a
|
||||
@ -1,4 +0,0 @@
|
||||
ro.product.manufacturer=Xiaomi
|
||||
ro.product.model=Redmi Note 12
|
||||
ro.board.platform=sm6225
|
||||
ro.hardware=qcom
|
||||
@ -1,75 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Assertion helpers and fixture drivers for the mtk_root fixture tests.
|
||||
#
|
||||
# Split out of run_tests.sh to keep it under the 250-line cap. The seam passes
|
||||
# state deliberately: pass/fail increment the caller's PASS and FAIL counters,
|
||||
# and run_recon reads WORKROOT, FIXTURES and RECON. The caller defines all of
|
||||
# them before sourcing this file.
|
||||
# ============================================================================
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
pass() {
|
||||
PASS=$((PASS + 1))
|
||||
printf ' [PASS] %s\n' "$1"
|
||||
}
|
||||
|
||||
fail() {
|
||||
FAIL=$((FAIL + 1))
|
||||
printf ' [FAIL] %s\n' "$1"
|
||||
[[ -n ${2:-} ]] && printf ' %s\n' "$2"
|
||||
return 0
|
||||
}
|
||||
|
||||
assert_contains() {
|
||||
if [[ $2 == *"$3"* ]]; then
|
||||
pass "$1"
|
||||
else
|
||||
fail "$1" "expected to find: $3"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_not_contains() {
|
||||
if [[ $2 != *"$3"* ]]; then
|
||||
pass "$1"
|
||||
else
|
||||
fail "$1" "did not expect: $3"
|
||||
fi
|
||||
}
|
||||
|
||||
assert_eq() {
|
||||
if [[ $2 == "$3" ]]; then
|
||||
pass "$1"
|
||||
else
|
||||
fail "$1" "expected '$3', got '$2'"
|
||||
fi
|
||||
}
|
||||
|
||||
# strip_noncode <file>
|
||||
# Reduce a script to the lines that could actually execute something, so the
|
||||
# read-only scan below can look for dangerous verbs in command position.
|
||||
#
|
||||
# Drops comments, and drops the ARGUMENTS of output builtins only - 10-recon.sh
|
||||
# exists to talk about flashing and unlocking, so those words appear
|
||||
# legitimately inside printf/echo text. Everything else keeps its quotes: an
|
||||
# earlier version stripped ALL quoted strings, which let
|
||||
# `mtk_adb shell "reboot bootloader"` slip through the scan entirely.
|
||||
strip_noncode() {
|
||||
sed -E \
|
||||
-e 's/(^|[[:space:]])#.*$/\1/' \
|
||||
-e 's/^[[:space:]]*(printf|echo|cat)[[:space:]].*$//' \
|
||||
-e 's/\|[[:space:]]*(printf|echo|cat)[[:space:]].*$//' \
|
||||
"$1"
|
||||
}
|
||||
|
||||
# run_recon <fixture-name> [extra args...] - prints combined output.
|
||||
run_recon() {
|
||||
local fixture="$1"
|
||||
shift
|
||||
MTK_ROOT_FIXTURE="$FIXTURES/$fixture" \
|
||||
MTK_ROOT_CACHE="$WORKROOT/cache" \
|
||||
MTK_SERIAL="TEST$RANDOM" \
|
||||
"$RECON" "$@" 2>&1
|
||||
}
|
||||
@ -1,157 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Fixture tests for 20-dump-stock.sh: refusals, the happy path, truncation
|
||||
# detection, ordering against 10-recon.sh, and the facts file it writes.
|
||||
#
|
||||
# Split out of run_tests.sh to keep both files under the 250-line cap. Sourced
|
||||
# by run_tests.sh, which owns the counters and the fixture paths these use.
|
||||
# ============================================================================
|
||||
|
||||
# shellcheck shell=bash
|
||||
|
||||
test_dump_refuses_non_mediatek() {
|
||||
printf '\n20-dump-stock.sh refuses a non-MediaTek device\n'
|
||||
[[ -x $DUMP ]] || {
|
||||
fail "20-dump-stock.sh present" "not found or not executable"
|
||||
return 0
|
||||
}
|
||||
|
||||
local out="" rc=0
|
||||
out="$(MTK_ROOT_FIXTURE="$FIXTURES/pixel-boot-ab" \
|
||||
MTK_ROOT_CACHE="$WORKROOT/cache" \
|
||||
MTK_SERIAL="TESTPIXEL" \
|
||||
"$DUMP" --yes 2>&1)" || rc=$?
|
||||
|
||||
assert_eq "exits non-zero on Pixel" "$rc" "1"
|
||||
assert_contains "refuses explicitly" "$out" "refus"
|
||||
assert_not_contains "no dump attempted" "$out" "Dumping"
|
||||
}
|
||||
|
||||
test_dump_happy_path() {
|
||||
printf '\n20-dump-stock.sh produces a manifest from a stubbed read\n'
|
||||
[[ -x $DUMP ]] || {
|
||||
fail "20-dump-stock.sh present" "not found or not executable"
|
||||
return 0
|
||||
}
|
||||
|
||||
# Stub stands in for mtkclient: writes a plausibly-sized file. This exercises
|
||||
# find_latest_facts, read_facts_value, verify_dump, the size band, the sha256
|
||||
# column and the manifest - none of which the refusal test reaches.
|
||||
local stub="$WORKROOT/stub-dump.sh"
|
||||
cat >"$stub" <<'STUB'
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
truncate -s 8M "$2"
|
||||
printf 'ANDROID!' | dd of="$2" conv=notrunc status=none
|
||||
STUB
|
||||
chmod +x "$stub"
|
||||
|
||||
local cache="$WORKROOT/dumpcache"
|
||||
mkdir -p "$cache"
|
||||
local serial="DUMPTEST1"
|
||||
|
||||
# Chain the real recon output in, rather than hand-writing a facts file:
|
||||
# this also proves the two scripts agree on the format.
|
||||
MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \
|
||||
MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" "$RECON" >/dev/null 2>&1
|
||||
|
||||
local out="" rc=0
|
||||
out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \
|
||||
MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" \
|
||||
MTK_DUMP_CMD="$stub" "$DUMP" --yes 2>&1)" || rc=$?
|
||||
|
||||
assert_eq "exits zero on success" "$rc" "0"
|
||||
assert_contains "dumped the init_boot partition" "$out" "init_boot_a"
|
||||
|
||||
local manifest="$cache/stock/$serial/manifest.txt"
|
||||
if [[ ! -f $manifest ]]; then
|
||||
fail "manifest written" "not found at $manifest"
|
||||
return 0
|
||||
fi
|
||||
pass "manifest written"
|
||||
|
||||
local content=""
|
||||
content="$(cat "$manifest")"
|
||||
assert_contains "manifest names the image" "$content" "init_boot_a.img"
|
||||
assert_contains "manifest records vbmeta" "$content" "vbmeta_a.img"
|
||||
assert_contains "manifest records carrier" "$content" "ramdisk_carrier=init_boot"
|
||||
assert_contains "manifest records size" "$content" "8388608 bytes"
|
||||
# A sha256 is 64 hex chars; assert one is actually present.
|
||||
if grep -qE '^[0-9a-f]{64} ' <<<"$content"; then
|
||||
pass "manifest records sha256"
|
||||
else
|
||||
fail "manifest records sha256" "no 64-char hex digest found"
|
||||
fi
|
||||
|
||||
# Idempotence: a second run must not silently re-dump over a good backup.
|
||||
out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \
|
||||
MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" \
|
||||
MTK_DUMP_CMD="$stub" "$DUMP" --yes 2>&1)" || true
|
||||
assert_contains "second run is a no-op" "$out" "already exists"
|
||||
}
|
||||
|
||||
test_dump_rejects_truncated_image() {
|
||||
printf '\n20-dump-stock.sh flags an implausibly small dump\n'
|
||||
[[ -x $DUMP ]] || return 0
|
||||
|
||||
# mtkclient exits 0 on a truncated read, so size is the only signal that the
|
||||
# "backup" is worthless. A 512-byte init_boot must not pass silently.
|
||||
local stub="$WORKROOT/stub-truncated.sh"
|
||||
cat >"$stub" <<'STUB'
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
truncate -s 512 "$2"
|
||||
STUB
|
||||
chmod +x "$stub"
|
||||
|
||||
local cache="$WORKROOT/truncache"
|
||||
mkdir -p "$cache"
|
||||
local serial="DUMPTEST2"
|
||||
|
||||
MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \
|
||||
MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" "$RECON" >/dev/null 2>&1
|
||||
|
||||
local out="" rc=0
|
||||
out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \
|
||||
MTK_ROOT_CACHE="$cache" MTK_SERIAL="$serial" \
|
||||
MTK_DUMP_CMD="$stub" "$DUMP" --yes 2>&1)" || rc=$?
|
||||
|
||||
assert_eq "exits non-zero" "$rc" "1"
|
||||
assert_contains "calls it implausibly small" "$out" "implausibly small"
|
||||
assert_contains "warns against trusting it" "$out" "Do NOT treat this as a usable backup"
|
||||
}
|
||||
|
||||
test_dump_requires_recon_first() {
|
||||
printf '\n20-dump-stock.sh refuses without a recon facts file\n'
|
||||
[[ -x $DUMP ]] || return 0
|
||||
|
||||
local out="" rc=0
|
||||
out="$(MTK_ROOT_FIXTURE="$FIXTURES/ulefone-initboot-ab" \
|
||||
MTK_ROOT_CACHE="$WORKROOT/emptycache" MTK_SERIAL="NOFACTS1" \
|
||||
MTK_DUMP_CMD="/bin/true" "$DUMP" --yes 2>&1)" || rc=$?
|
||||
|
||||
assert_eq "exits non-zero" "$rc" "1"
|
||||
assert_contains "points at 10-recon.sh" "$out" "10-recon.sh"
|
||||
assert_contains "explains it will not guess" "$out" "rather than guessing"
|
||||
}
|
||||
|
||||
test_facts_file_written() {
|
||||
printf '\nfacts file is written and machine-readable\n'
|
||||
local out="" facts=""
|
||||
out="$(run_recon ulefone-initboot-ab)"
|
||||
|
||||
facts="$(find "$WORKROOT/cache" -name 'device-facts-ULEFONE-*.txt' -print -quit 2>/dev/null)"
|
||||
if [[ -z $facts ]]; then
|
||||
fail "facts file created" "none found under $WORKROOT/cache"
|
||||
return 0
|
||||
fi
|
||||
pass "facts file created"
|
||||
|
||||
local content=""
|
||||
content="$(cat "$facts")"
|
||||
assert_contains "records ramdisk partition" "$content" "ramdisk_partition=init_boot_a"
|
||||
assert_contains "records carrier" "$content" "ramdisk_carrier=init_boot"
|
||||
assert_contains "records scheme" "$content" "ab_scheme=A/B"
|
||||
assert_contains "includes by-name listing" "$content" "[by-name]"
|
||||
}
|
||||
@ -1,218 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# ============================================================================
|
||||
# Fixture tests for the mtk_root toolkit.
|
||||
#
|
||||
# Every test runs against a fixture directory (props.txt + by-name.txt) via
|
||||
# MTK_ROOT_FIXTURE, so no phone is ever touched and the suite is hermetic.
|
||||
#
|
||||
# What these tests DO prove: the partition-selection logic, the classification
|
||||
# refusals, and the read-only guarantee of 10-recon.sh.
|
||||
# What they do NOT prove: that mtkclient can read any partition on real
|
||||
# hardware. Fixtures cannot establish that.
|
||||
# ============================================================================
|
||||
|
||||
set -uo pipefail
|
||||
|
||||
SCRIPT_DIR="$(dirname "$(readlink -f "$0")")"
|
||||
readonly SCRIPT_DIR
|
||||
readonly TOOLKIT_DIR="$SCRIPT_DIR/.."
|
||||
readonly FIXTURES="$SCRIPT_DIR/fixtures"
|
||||
readonly RECON="$TOOLKIT_DIR/10-recon.sh"
|
||||
readonly DUMP="$TOOLKIT_DIR/20-dump-stock.sh"
|
||||
|
||||
PASS=0
|
||||
FAIL=0
|
||||
WORKROOT=""
|
||||
|
||||
cleanup() {
|
||||
[[ -n $WORKROOT && -d $WORKROOT ]] && rm -rf "$WORKROOT"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
|
||||
# shellcheck source=./lib/assertions.sh
|
||||
source "$SCRIPT_DIR/lib/assertions.sh"
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
test_initboot_preferred_over_boot() {
|
||||
printf '\ninit_boot is chosen over a co-present boot\n'
|
||||
local out=""
|
||||
out="$(run_recon ulefone-initboot-ab)"
|
||||
|
||||
# The decisive case: this fixture has BOTH boot_a and init_boot_a. Choosing
|
||||
# boot_a here would produce a device that does not boot after flashing.
|
||||
assert_contains "selects init_boot_a" "$out" "partition init_boot_a"
|
||||
assert_contains "instructs patching INIT_BOOT" "$out" "must patch INIT_BOOT"
|
||||
assert_not_contains "does not select boot_a" "$out" "partition boot_a"
|
||||
assert_contains "detects A/B" "$out" "A/B scheme : A/B"
|
||||
assert_contains "vbmeta suffixed" "$out" "vbmeta_a"
|
||||
}
|
||||
|
||||
test_a_only_scheme() {
|
||||
printf '\nA-only devices get bare partition names\n'
|
||||
local out=""
|
||||
out="$(run_recon ulefone-initboot-aonly)"
|
||||
|
||||
assert_contains "detects A-only" "$out" "A-only"
|
||||
assert_contains "bare init_boot" "$out" "partition init_boot"
|
||||
assert_contains "bare vbmeta" "$out" "vbmeta : vbmeta"
|
||||
}
|
||||
|
||||
test_boot_carrier_when_no_initboot() {
|
||||
printf '\nfalls back to boot when init_boot is absent\n'
|
||||
local out=""
|
||||
out="$(run_recon pixel-boot-ab)"
|
||||
|
||||
assert_contains "selects boot_a" "$out" "partition boot_a"
|
||||
assert_contains "instructs patching BOOT" "$out" "must patch BOOT"
|
||||
# The word "init_boot" legitimately appears in the explanatory text ("no
|
||||
# init_boot present"), so assert on the selection line specifically rather
|
||||
# than on the whole report.
|
||||
local carrier_line=""
|
||||
carrier_line="$(grep 'ramdisk lives in' <<<"$out" || true)"
|
||||
assert_contains "carrier reported as boot" "$carrier_line" "ramdisk lives in: boot"
|
||||
assert_not_contains "carrier is not init_boot" "$carrier_line" "init_boot"
|
||||
}
|
||||
|
||||
test_unknown_device_refused() {
|
||||
printf '\nunknown device is refused, never assumed\n'
|
||||
local out="" rc=0
|
||||
out="$(run_recon unknown-device)" || rc=$?
|
||||
|
||||
assert_eq "exits non-zero" "$rc" "1"
|
||||
assert_contains "says NO PATH" "$out" "NO PATH"
|
||||
assert_contains "refuses to guess" "$out" "Refusing to guess"
|
||||
# The specific danger: treating "not a Pixel" as "therefore the Ulefone".
|
||||
assert_not_contains "does not claim PATH A" "$out" "PATH A"
|
||||
assert_not_contains "does not claim PATH B" "$out" "PATH B"
|
||||
}
|
||||
|
||||
test_pixel_is_protected() {
|
||||
printf '\nPixel is recognised and refused as a target\n'
|
||||
local out=""
|
||||
out="$(run_recon pixel-boot-ab)"
|
||||
|
||||
assert_contains "classified PIXEL" "$out" "classified as : PIXEL"
|
||||
assert_contains "indeterminate C/D" "$out" "PATH C or D"
|
||||
assert_contains "will not act on it" "$out" "will not act on a Pixel"
|
||||
}
|
||||
|
||||
test_carrier_lock_never_asserted() {
|
||||
printf '\ncarrier lock is reported as undetermined, never negative\n'
|
||||
local out=""
|
||||
out="$(run_recon pixel-boot-ab)"
|
||||
|
||||
assert_contains "reports UNDETERMINED" "$out" "Carrier lock : UNDETERMINED"
|
||||
assert_contains "explains why" "$out" "NOT the same"
|
||||
# A false "not carrier locked" would wrongly rule a path back in.
|
||||
assert_not_contains "no false negative" "$out" "not carrier locked"
|
||||
}
|
||||
|
||||
test_assumption_banner_always_shown() {
|
||||
printf '\npath semantics are flagged as assumed on every run\n'
|
||||
local out=""
|
||||
for fixture in ulefone-initboot-ab pixel-boot-ab unknown-device; do
|
||||
out="$(run_recon "$fixture")" || true
|
||||
assert_contains "banner present ($fixture)" "$out" "PATH SEMANTICS ARE ASSUMED"
|
||||
done
|
||||
}
|
||||
|
||||
test_recon_is_read_only() {
|
||||
printf '\n10-recon.sh contains no mutating command\n'
|
||||
|
||||
local code="" hits=""
|
||||
code="$(strip_noncode "$RECON")"
|
||||
|
||||
hits="$(grep -nE '(^|[^[:alnum:]_.-])(fastboot|magiskboot|mtk\.py)([^[:alnum:]_-]|$)' <<<"$code" || true)"
|
||||
assert_eq "no fastboot/magiskboot invocation" "${hits:-none}" "none"
|
||||
|
||||
hits="$(grep -nE 'adb[^|]*(reboot|push|install|remount)' <<<"$code" || true)"
|
||||
assert_eq "no adb reboot/push/install/remount" "${hits:-none}" "none"
|
||||
|
||||
hits="$(grep -nE 'settings[[:space:]]+put|svc[[:space:]]+(power|data)' <<<"$code" || true)"
|
||||
assert_eq "no settings put / svc" "${hits:-none}" "none"
|
||||
|
||||
hits="$(grep -nE '\bdd[[:space:]]+if=|\bmkfs|\berase\b|flashing[[:space:]]+unlock' <<<"$code" || true)"
|
||||
assert_eq "no dd/mkfs/erase/unlock" "${hits:-none}" "none"
|
||||
|
||||
# Belt and braces: the only adb subcommands this script may ever run.
|
||||
local subcommands=""
|
||||
subcommands="$(grep -oE 'mtk_adb[[:space:]]+[a-z-]+' "$RECON" | awk '{ print $2 }' | sort -u | tr '\n' ' ')"
|
||||
assert_eq "mtk_adb used only for shell" "${subcommands% }" "shell"
|
||||
}
|
||||
|
||||
test_read_only_scan_catches_injection() {
|
||||
printf '\nthe read-only scan actually fails on a mutating command\n'
|
||||
|
||||
# A guard that has never been observed failing is not known to work. Inject
|
||||
# real dangerous commands into a copy and assert each is caught. The quoted
|
||||
# form is the specific case a previous version of this scan let through.
|
||||
local evil="$WORKROOT/evil.sh" code="" hits=""
|
||||
|
||||
sed 's|^ classify_device$| mtk_adb shell "reboot bootloader"\n classify_device|' \
|
||||
"$RECON" >"$evil"
|
||||
code="$(strip_noncode "$evil")"
|
||||
hits="$(grep -cE 'adb[^|]*(reboot|push|install|remount)' <<<"$code" || true)"
|
||||
if [[ ${hits:-0} -gt 0 ]]; then
|
||||
pass "catches quoted 'adb shell \"reboot ...\"'"
|
||||
else
|
||||
fail "catches quoted 'adb shell \"reboot ...\"'" "injection went undetected"
|
||||
fi
|
||||
|
||||
sed 's|^ classify_device$| fastboot flashing unlock\n classify_device|' \
|
||||
"$RECON" >"$evil"
|
||||
code="$(strip_noncode "$evil")"
|
||||
hits="$(grep -cE '(^|[^[:alnum:]_.-])(fastboot|magiskboot|mtk\.py)([^[:alnum:]_-]|$)' <<<"$code" || true)"
|
||||
if [[ ${hits:-0} -gt 0 ]]; then
|
||||
pass "catches bare fastboot invocation"
|
||||
else
|
||||
fail "catches bare fastboot invocation" "injection went undetected"
|
||||
fi
|
||||
|
||||
sed 's|^ classify_device$| mtk_adb shell settings put global foo 1\n classify_device|' \
|
||||
"$RECON" >"$evil"
|
||||
code="$(strip_noncode "$evil")"
|
||||
hits="$(grep -cE 'settings[[:space:]]+put|svc[[:space:]]+(power|data)' <<<"$code" || true)"
|
||||
if [[ ${hits:-0} -gt 0 ]]; then
|
||||
pass "catches settings put"
|
||||
else
|
||||
fail "catches settings put" "injection went undetected"
|
||||
fi
|
||||
|
||||
# And the scan must still pass on the real, unmodified script.
|
||||
code="$(strip_noncode "$RECON")"
|
||||
hits="$(grep -nE 'adb[^|]*(reboot|push|install|remount)' <<<"$code" || true)"
|
||||
assert_eq "no false positive on the real script" "${hits:-none}" "none"
|
||||
}
|
||||
|
||||
# shellcheck source=./lib/dump_tests.sh
|
||||
source "$SCRIPT_DIR/lib/dump_tests.sh"
|
||||
|
||||
main() {
|
||||
WORKROOT="$(mktemp -d)"
|
||||
readonly WORKROOT
|
||||
mkdir -p "$WORKROOT/cache"
|
||||
|
||||
printf '=== mtk_root fixture tests ===\n'
|
||||
|
||||
test_initboot_preferred_over_boot
|
||||
test_a_only_scheme
|
||||
test_boot_carrier_when_no_initboot
|
||||
test_unknown_device_refused
|
||||
test_pixel_is_protected
|
||||
test_carrier_lock_never_asserted
|
||||
test_assumption_banner_always_shown
|
||||
test_recon_is_read_only
|
||||
test_read_only_scan_catches_injection
|
||||
test_dump_refuses_non_mediatek
|
||||
test_dump_happy_path
|
||||
test_dump_rejects_truncated_image
|
||||
test_dump_requires_recon_first
|
||||
test_facts_file_written
|
||||
|
||||
printf '\n=== %d passed, %d failed ===\n' "$PASS" "$FAIL"
|
||||
[[ $FAIL -eq 0 ]]
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@ -1,24 +0,0 @@
|
||||
# mtk_root - USB access for Android debugging and MediaTek BROM recovery.
|
||||
#
|
||||
# Installed by mtk_root/install.sh. Scoped to two vendor IDs on purpose.
|
||||
#
|
||||
# MODE="0660" plus GROUP + TAG+="uaccess", NOT MODE="0666":
|
||||
# - 0666 grants every process on the machine read/write access to the device
|
||||
# - uaccess grants access to the user logged in at the physical seat, which
|
||||
# is what is actually wanted, and it is revoked on logout
|
||||
# A rule matching idVendor=="*" with 0666 applies to every USB device attached
|
||||
# to the system - storage, security keys, everything - not just phones.
|
||||
|
||||
# Google (Pixel): adb + fastboot
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="18d1", MODE="0660", GROUP="adbusers", TAG+="uaccess"
|
||||
|
||||
# MediaTek: BROM / preloader / DA modes, used by mtkclient.
|
||||
# 0e8d covers the BROM (2000), preloader (2001) and DA download ports.
|
||||
SUBSYSTEM=="usb", ATTR{idVendor}=="0e8d", MODE="0660", GROUP="adbusers", TAG+="uaccess"
|
||||
|
||||
# MediaTek devices also expose a CDC-ACM serial port in preloader mode;
|
||||
# mtkclient opens it directly.
|
||||
SUBSYSTEM=="tty", ATTRS{idVendor}=="0e8d", MODE="0660", GROUP="uucp", TAG+="uaccess"
|
||||
|
||||
# Ulefone's own USB vendor ID, if it differs from MediaTek's, is UNKNOWN until
|
||||
# the device is attached: run `lsusb` with it plugged in and add a line here.
|
||||
@ -15,10 +15,6 @@ linux_configuration/.githooks/lib/common.sh
|
||||
linux_configuration/scripts/meta/lib/shell_check_discover.sh
|
||||
linux_configuration/scripts/meta/lib/shell_check_install.sh
|
||||
linux_configuration/scripts/meta/lib/shell_check_lint.sh
|
||||
linux_configuration/scripts/mtk_root/lib/dump_partitions.sh
|
||||
linux_configuration/scripts/mtk_root/lib/dump_verify.sh
|
||||
linux_configuration/scripts/mtk_root/lib/mtk_udev.sh
|
||||
linux_configuration/scripts/mtk_root/lib/recon_report.sh
|
||||
linux_configuration/scripts/periodic_background/digital_wellbeing/pacman/lib/install_files.sh
|
||||
linux_configuration/scripts/periodic_background/digital_wellbeing/pacman/lib/integrity.sh
|
||||
linux_configuration/scripts/periodic_background/digital_wellbeing/pacman/lib/managed_copy.sh
|
||||
|
||||
Loading…
Reference in New Issue
Block a user