diff --git a/AGENTS.md b/AGENTS.md index a27699bb..c30cc09e 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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 diff --git a/docs/superpowers/contracts/extract-mtk-root-2026-08-23.json b/docs/superpowers/contracts/extract-mtk-root-2026-08-23.json new file mode 100644 index 00000000..73037ee6 --- /dev/null +++ b/docs/superpowers/contracts/extract-mtk-root-2026-08-23.json @@ -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." +} diff --git a/docs/superpowers/evidence/extract-mtk-root-2026-08-23.json b/docs/superpowers/evidence/extract-mtk-root-2026-08-23.json new file mode 100644 index 00000000..0ce4fe63 --- /dev/null +++ b/docs/superpowers/evidence/extract-mtk-root-2026-08-23.json @@ -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." + ] +} diff --git a/linux_configuration/scripts/lib/mtk_classify.sh b/linux_configuration/scripts/lib/mtk_classify.sh deleted file mode 100755 index 558d7a6d..00000000 --- a/linux_configuration/scripts/lib/mtk_classify.sh +++ /dev/null @@ -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 -} diff --git a/linux_configuration/scripts/lib/mtk_common.sh b/linux_configuration/scripts/lib/mtk_common.sh deleted file mode 100755 index 9373d029..00000000 --- a/linux_configuration/scripts/lib/mtk_common.sh +++ /dev/null @@ -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 -# 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" diff --git a/linux_configuration/scripts/lib/mtk_device.sh b/linux_configuration/scripts/lib/mtk_device.sh deleted file mode 100755 index 36892ab2..00000000 --- a/linux_configuration/scripts/lib/mtk_device.sh +++ /dev/null @@ -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 - 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 " " 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 - 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 ." - 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 -} diff --git a/linux_configuration/scripts/lib/mtk_partitions.sh b/linux_configuration/scripts/lib/mtk_partitions.sh deleted file mode 100755 index f452fd6f..00000000 --- a/linux_configuration/scripts/lib/mtk_partitions.sh +++ /dev/null @@ -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 -mtk_sha256() { - sha256sum "$1" | awk '{ print $1 }' -} - -# mtk_file_size -mtk_file_size() { - stat -c '%s' "$1" -} - -# mtk_sane_ramdisk_size -# 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 -} diff --git a/linux_configuration/scripts/lib/tests/mtk_harness.sh b/linux_configuration/scripts/lib/tests/mtk_harness.sh index 73f881bf..04a463e6 100755 --- a/linux_configuration/scripts/lib/tests/mtk_harness.sh +++ b/linux_configuration/scripts/lib/tests/mtk_harness.sh @@ -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 diff --git a/linux_configuration/scripts/lib/tests/run_all.sh b/linux_configuration/scripts/lib/tests/run_all.sh index f4eeeecb..32abc744 100755 --- a/linux_configuration/scripts/lib/tests/run_all.sh +++ b/linux_configuration/scripts/lib/tests/run_all.sh @@ -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 diff --git a/linux_configuration/scripts/lib/tests/test_mtk_classify.sh b/linux_configuration/scripts/lib/tests/test_mtk_classify.sh deleted file mode 100755 index 978c3c67..00000000 --- a/linux_configuration/scripts/lib/tests/test_mtk_classify.sh +++ /dev/null @@ -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" diff --git a/linux_configuration/scripts/lib/tests/test_mtk_device.sh b/linux_configuration/scripts/lib/tests/test_mtk_device.sh deleted file mode 100755 index d2a6ac18..00000000 --- a/linux_configuration/scripts/lib/tests/test_mtk_device.sh +++ /dev/null @@ -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" diff --git a/linux_configuration/scripts/lib/tests/test_mtk_partitions.sh b/linux_configuration/scripts/lib/tests/test_mtk_partitions.sh deleted file mode 100755 index 8c206bcd..00000000 --- a/linux_configuration/scripts/lib/tests/test_mtk_partitions.sh +++ /dev/null @@ -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" diff --git a/linux_configuration/scripts/mtk_root/00-preflight.sh b/linux_configuration/scripts/mtk_root/00-preflight.sh deleted file mode 100755 index a1302e63..00000000 --- a/linux_configuration/scripts/mtk_root/00-preflight.sh +++ /dev/null @@ -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 </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 diff --git a/linux_configuration/scripts/mtk_root/10-recon.sh b/linux_configuration/scripts/mtk_root/10-recon.sh deleted file mode 100755 index 916083c6..00000000 --- a/linux_configuration/scripts/mtk_root/10-recon.sh +++ /dev/null @@ -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 <] [--explain-classification] [--help] - -Reads device facts read-only and prints a path decision summary. -Writes a facts file to \$(mtk_cache_dir)/device-facts---.txt - - --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= 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:-}" - printf ' model : %s\n' "${MTK_PROP_MODEL:-}" - printf ' platform : %s / %s\n' "${MTK_PROP_PLATFORM:-}" "${MTK_PROP_HARDWARE:-}" - 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 diff --git a/linux_configuration/scripts/mtk_root/20-dump-stock.sh b/linux_configuration/scripts/mtk_root/20-dump-stock.sh deleted file mode 100755 index 407a0230..00000000 --- a/linux_configuration/scripts/mtk_root/20-dump-stock.sh +++ /dev/null @@ -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 <] [--yes] [--force] [--help] - -Dumps the stock ramdisk-carrying partition and vbmeta from a MediaTek device -using mtkclient, then records sizes and sha256 sums. - - --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 -# 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:-}" - 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 diff --git a/linux_configuration/scripts/mtk_root/90-verify-root.sh b/linux_configuration/scripts/mtk_root/90-verify-root.sh deleted file mode 100755 index 04510b8d..00000000 --- a/linux_configuration/scripts/mtk_root/90-verify-root.sh +++ /dev/null @@ -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 <] [--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:-}" - 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:-}" - 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:-}" - ;; - 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:-}" - 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 diff --git a/linux_configuration/scripts/mtk_root/NOTES.md b/linux_configuration/scripts/mtk_root/NOTES.md deleted file mode 100644 index 2a62bbca..00000000 --- a/linux_configuration/scripts/mtk_root/NOTES.md +++ /dev/null @@ -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 ` 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. diff --git a/linux_configuration/scripts/mtk_root/README.md b/linux_configuration/scripts/mtk_root/README.md deleted file mode 100644 index 0c886971..00000000 --- a/linux_configuration/scripts/mtk_root/README.md +++ /dev/null @@ -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 ` 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. diff --git a/linux_configuration/scripts/mtk_root/READY.md b/linux_configuration/scripts/mtk_root/READY.md deleted file mode 100644 index 22047b87..00000000 --- a/linux_configuration/scripts/mtk_root/READY.md +++ /dev/null @@ -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` diff --git a/linux_configuration/scripts/mtk_root/install.sh b/linux_configuration/scripts/mtk_root/install.sh deleted file mode 100755 index 930cc994..00000000 --- a/linux_configuration/scripts/mtk_root/install.sh +++ /dev/null @@ -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 </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 diff --git a/linux_configuration/scripts/mtk_root/lib/dump_partitions.sh b/linux_configuration/scripts/mtk_root/lib/dump_partitions.sh deleted file mode 100755 index 685144c7..00000000 --- a/linux_configuration/scripts/mtk_root/lib/dump_partitions.sh +++ /dev/null @@ -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 -# -# MTK_DUMP_CMD overrides the mtkclient invocation and is the seam the tests -# use: it is called as `$MTK_DUMP_CMD `. 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 ` 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" - ) -} diff --git a/linux_configuration/scripts/mtk_root/lib/dump_verify.sh b/linux_configuration/scripts/mtk_root/lib/dump_verify.sh deleted file mode 100755 index 4cba281a..00000000 --- a/linux_configuration/scripts/mtk_root/lib/dump_verify.sh +++ /dev/null @@ -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 -} diff --git a/linux_configuration/scripts/mtk_root/lib/mtk_udev.sh b/linux_configuration/scripts/mtk_root/lib/mtk_udev.sh deleted file mode 100755 index 1880eb2d..00000000 --- a/linux_configuration/scripts/mtk_root/lib/mtk_udev.sh +++ /dev/null @@ -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." -} diff --git a/linux_configuration/scripts/mtk_root/lib/recon_report.sh b/linux_configuration/scripts/mtk_root/lib/recon_report.sh deleted file mode 100755 index a55bb5eb..00000000 --- a/linux_configuration/scripts/mtk_root/lib/recon_report.sh +++ /dev/null @@ -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:-}" - printf ' ro.boot.verifiedbootstate : %s\n' "${UNLOCK_VBSTATE:-}" - printf ' ro.boot.vbmeta.device_state : %s\n' "${UNLOCK_DEVICE_STATE:-}" - printf ' sys.oem_unlock_allowed : %s\n' "${UNLOCK_SYS_ALLOWED:-}" - printf ' ro.oem_unlock_supported : %s\n' "${UNLOCK_OEM_SUPPORTED:-}" - printf ' settings oem_unlock_allowed : %s\n' "${UNLOCK_SETTING:-}" - 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:-}" - 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 -} diff --git a/linux_configuration/scripts/mtk_root/tests/fixtures/pixel-boot-ab/by-name.txt b/linux_configuration/scripts/mtk_root/tests/fixtures/pixel-boot-ab/by-name.txt deleted file mode 100644 index d0f97dff..00000000 --- a/linux_configuration/scripts/mtk_root/tests/fixtures/pixel-boot-ab/by-name.txt +++ /dev/null @@ -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 diff --git a/linux_configuration/scripts/mtk_root/tests/fixtures/pixel-boot-ab/props.txt b/linux_configuration/scripts/mtk_root/tests/fixtures/pixel-boot-ab/props.txt deleted file mode 100644 index f2f6aed7..00000000 --- a/linux_configuration/scripts/mtk_root/tests/fixtures/pixel-boot-ab/props.txt +++ /dev/null @@ -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 diff --git a/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-ab/by-name.txt b/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-ab/by-name.txt deleted file mode 100644 index b48a99e9..00000000 --- a/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-ab/by-name.txt +++ /dev/null @@ -1,8 +0,0 @@ -boot_a -boot_b -init_boot_a -init_boot_b -vbmeta_a -vbmeta_b -super -userdata diff --git a/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-ab/props.txt b/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-ab/props.txt deleted file mode 100644 index dfb10fca..00000000 --- a/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-ab/props.txt +++ /dev/null @@ -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 diff --git a/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-aonly/by-name.txt b/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-aonly/by-name.txt deleted file mode 100644 index 37b13777..00000000 --- a/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-aonly/by-name.txt +++ /dev/null @@ -1,5 +0,0 @@ -boot -init_boot -vbmeta -super -userdata diff --git a/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-aonly/props.txt b/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-aonly/props.txt deleted file mode 100644 index 8a03d407..00000000 --- a/linux_configuration/scripts/mtk_root/tests/fixtures/ulefone-initboot-aonly/props.txt +++ /dev/null @@ -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 diff --git a/linux_configuration/scripts/mtk_root/tests/fixtures/unknown-device/by-name.txt b/linux_configuration/scripts/mtk_root/tests/fixtures/unknown-device/by-name.txt deleted file mode 100644 index 18d84fe7..00000000 --- a/linux_configuration/scripts/mtk_root/tests/fixtures/unknown-device/by-name.txt +++ /dev/null @@ -1,2 +0,0 @@ -boot_a -vbmeta_a diff --git a/linux_configuration/scripts/mtk_root/tests/fixtures/unknown-device/props.txt b/linux_configuration/scripts/mtk_root/tests/fixtures/unknown-device/props.txt deleted file mode 100644 index 692213fe..00000000 --- a/linux_configuration/scripts/mtk_root/tests/fixtures/unknown-device/props.txt +++ /dev/null @@ -1,4 +0,0 @@ -ro.product.manufacturer=Xiaomi -ro.product.model=Redmi Note 12 -ro.board.platform=sm6225 -ro.hardware=qcom diff --git a/linux_configuration/scripts/mtk_root/tests/lib/assertions.sh b/linux_configuration/scripts/mtk_root/tests/lib/assertions.sh deleted file mode 100755 index 7582200d..00000000 --- a/linux_configuration/scripts/mtk_root/tests/lib/assertions.sh +++ /dev/null @@ -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 -# 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 [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 -} diff --git a/linux_configuration/scripts/mtk_root/tests/lib/dump_tests.sh b/linux_configuration/scripts/mtk_root/tests/lib/dump_tests.sh deleted file mode 100755 index ec322960..00000000 --- a/linux_configuration/scripts/mtk_root/tests/lib/dump_tests.sh +++ /dev/null @@ -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]" -} diff --git a/linux_configuration/scripts/mtk_root/tests/run_tests.sh b/linux_configuration/scripts/mtk_root/tests/run_tests.sh deleted file mode 100755 index 29100cc6..00000000 --- a/linux_configuration/scripts/mtk_root/tests/run_tests.sh +++ /dev/null @@ -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 "$@" diff --git a/linux_configuration/scripts/mtk_root/udev/60-mtk-root.rules b/linux_configuration/scripts/mtk_root/udev/60-mtk-root.rules deleted file mode 100644 index bd64fefe..00000000 --- a/linux_configuration/scripts/mtk_root/udev/60-mtk-root.rules +++ /dev/null @@ -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. diff --git a/meta/shell-coverage-allowlist.txt b/meta/shell-coverage-allowlist.txt index d34afa9c..8dba7fc0 100644 --- a/meta/shell-coverage-allowlist.txt +++ b/meta/shell-coverage-allowlist.txt @@ -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