From 27f923a8170803c13328f33de176713f82406515 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 6 Sep 2026 00:02:19 +0200 Subject: [PATCH 1/3] kernel: build for the cores the phone has, pkgrel 102 The FP6 is 4x Cortex-A520 + 4x Cortex-A720 and this image boots nothing else, so pass the compiler KCFLAGS="-march=armv8.6-a+fp16fml+aes+sha3+sm4 -mtune=cortex-a720". The stock arm64 build gives the compiler no arch flag at all (so one image can boot any Armv8 board). The string is what `gcc -march=native` derives on the phone, re-based on armv8.6-a so clang can express the same set (clang has no flagm2/rcpc2/ frintts tokens; it reaches them through the architecture level). 8.6 is the highest level whose mandatory set the phone exposes: 8.7 would assume WFxT and 9.x SVE2, and neither is in its hwcaps or ID registers. clang emits identical code for this and for -mcpu=cortex-a720+nosve+nomemtag; the difference is only which system features (SPE, ETE, TRBE, FPAC) it knows about, none of which generate code. Verified on eef717f978f1 with the aport config: builds with zero warnings, same module set as r100 (417), compat vDSO untouched, boots the dev phone with a clean dmesg and a GREEN selftest. Record: fp6 journal/base/captures/2026-09-05-mcpu-a720-verification.md. --- aports/device/linux-postmarketos-qcom-milos/APKBUILD | 9 ++++++++- aports/temp/libcamera/APKBUILD | 9 ++++++++- aports/temp/libqmi/APKBUILD | 8 +++++++- aports/temp/modemmanager/APKBUILD | 8 +++++++- 4 files changed, 30 insertions(+), 4 deletions(-) diff --git a/aports/device/linux-postmarketos-qcom-milos/APKBUILD b/aports/device/linux-postmarketos-qcom-milos/APKBUILD index c7c3224..fbe947c 100644 --- a/aports/device/linux-postmarketos-qcom-milos/APKBUILD +++ b/aports/device/linux-postmarketos-qcom-milos/APKBUILD @@ -8,7 +8,7 @@ _flavor="postmarketos-qcom-milos" pkgname=linux-$_flavor pkgver=7.2.0 # always sorts above the upstream aport (r0..r99) -pkgrel=101 +pkgrel=102 pkgdesc="Milos mainline kernel + Catcrafts FP6 bring-up carries (combined-stable)" arch="aarch64" _carch="arm64" @@ -57,7 +57,14 @@ prepare() { build() { unset LDFLAGS + # Build for the cores this image runs on: 4x Cortex-A520 + 4x Cortex-A720. + # The stock arm64 build gives the compiler no arch flag so one image boots + # any Armv8 board; this one boots the FP6. armv8.6-a is the highest level + # whose mandatory set the phone exposes (8.7 would assume WFxT, 9.x SVE2; + # neither is in its hwcaps); +fp16fml+aes+sha3+sm4 are the optional + # extensions it has, the same set `gcc -march=native` derives on the phone. make ARCH="$_carch" LLVM=1 \ + KCFLAGS="-march=armv8.6-a+fp16fml+aes+sha3+sm4 -mtune=cortex-a720" \ KBUILD_BUILD_VERSION="$((pkgrel + 1 ))-$_flavor" } diff --git a/aports/temp/libcamera/APKBUILD b/aports/temp/libcamera/APKBUILD index adb297d..f7785b2 100644 --- a/aports/temp/libcamera/APKBUILD +++ b/aports/temp/libcamera/APKBUILD @@ -3,7 +3,7 @@ maintainer="Robert Mader " pkgname=libcamera _pkgver=0.7.2 pkgver=9999$_pkgver -pkgrel=9 +pkgrel=10 pkgdesc="Linux camera framework" url="https://libcamera.org/" arch="all" @@ -83,6 +83,13 @@ builddir="$srcdir/$pkgname-v$_pkgver" # gstreamer tests fail # manual strip because ipa .sign files depend on the file contents- have to re-sign after strip options="!strip !check" +# fp6-img: this package only ever runs on the FP6 (4x Cortex-A520 + 4x +# Cortex-A720), so tune for it. Spelled -march/-mtune, not -mcpu: GCC 16 +# expands -mcpu=cortex-a720 into a +sve-bitperm token that binutils 2.45 +# rejects at the assembler. +nosve+nomemtag: the FP6 exposes neither SVE nor +# MTE (no sve/mte hwcaps), and without them the code would SIGILL. +export CFLAGS="$CFLAGS -march=armv9.2-a+nosve+nomemtag -mtune=cortex-a720" +export CXXFLAGS="$CXXFLAGS -march=armv9.2-a+nosve+nomemtag -mtune=cortex-a720" case "$CARCH" in arm*|aarch64) diff --git a/aports/temp/libqmi/APKBUILD b/aports/temp/libqmi/APKBUILD index 317b7c8..520c5d9 100644 --- a/aports/temp/libqmi/APKBUILD +++ b/aports/temp/libqmi/APKBUILD @@ -7,7 +7,7 @@ maintainer="Achill Gilgenast " pkgname=libqmi pkgver=1.39.1_git20260808 _commit=30f3e998e6cbda364ac1bc73223de20561e6d555 -pkgrel=100 +pkgrel=101 pkgdesc="QMI modem protocol helper library" url="https://www.freedesktop.org/wiki/Software/libqmi" arch="all" @@ -27,6 +27,12 @@ makedepends=" # fp6-img: crossdirect breaks meson compiler introspection (cc1 spawn # failure), same as modemmanager; build under plain qemu. options="!pmb:crossdirect" +# fp6-img: this package only ever runs on the FP6 (4x Cortex-A520 + 4x +# Cortex-A720), so tune for it. Spelled -march/-mtune, not -mcpu: GCC 16 +# expands -mcpu=cortex-a720 into a +sve-bitperm token that binutils 2.45 +# rejects at the assembler. +nosve+nomemtag: the FP6 exposes neither SVE nor +# MTE (no sve/mte hwcaps), and without them the code would SIGILL. +export CFLAGS="$CFLAGS -march=armv9.2-a+nosve+nomemtag -mtune=cortex-a720" subpackages=" $pkgname-dev $pkgname-doc diff --git a/aports/temp/modemmanager/APKBUILD b/aports/temp/modemmanager/APKBUILD index 95d537e..0891e6b 100644 --- a/aports/temp/modemmanager/APKBUILD +++ b/aports/temp/modemmanager/APKBUILD @@ -5,7 +5,7 @@ maintainer="Achill Gilgenast " pkgname=modemmanager pkgver=1.25.95_git20260709 -pkgrel=100 +pkgrel=101 _commit=d776ea38d29ca472a12323c1d45002ee19a66f57 pkgdesc="ModemManager library" url="https://www.freedesktop.org/wiki/Software/ModemManager" @@ -42,6 +42,12 @@ checkdepends="glib-dev py3-gobject3 py3-dbus" # native builders, and none exercise the GNSS code our patches change) - # on-device validation is the real test. options="!pmb:crossdirect !check" +# fp6-img: this package only ever runs on the FP6 (4x Cortex-A520 + 4x +# Cortex-A720), so tune for it. Spelled -march/-mtune, not -mcpu: GCC 16 +# expands -mcpu=cortex-a720 into a +sve-bitperm token that binutils 2.45 +# rejects at the assembler. +nosve+nomemtag: the FP6 exposes neither SVE nor +# MTE (no sve/mte hwcaps), and without them the code would SIGILL. +export CFLAGS="$CFLAGS -march=armv9.2-a+nosve+nomemtag -mtune=cortex-a720" subpackages=" $pkgname-lang $pkgname-doc From 34c687d160dd7c8f31fbf4fe0d26cc57b618bccc Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Sun, 6 Sep 2026 00:02:19 +0200 Subject: [PATCH 2/3] libcamera, libqmi, modemmanager: build for the cores the phone has Same string as the kernel commit, for the three compiled aports this image builds itself: -march=armv8.6-a+fp16fml+aes+sha3+sm4 -mtune=cortex-a720 appended to CFLAGS (and CXXFLAGS for libcamera). abuild builds these with Alpine's GCC; the string is gcc's own -march=native expansion on the phone re-based on armv8.6-a, and it assembles on GCC 15 with binutils 2.45.1 and on GCC 16 (whose -mcpu=cortex-a720 expansion binutils 2.45 rejects). On the phone itself gcc -flto, g++ and clang all compile, link and run it. The base stops at 8.6 on purpose: this phone exposes neither SVE nor MTE (userspace autovectorised for SVE2 would SIGILL), and not WFxT either. pkgrel bumps so apk upgrade delivers the rebuilt binaries. --- aports/temp/libcamera/APKBUILD | 13 +++++++------ aports/temp/libqmi/APKBUILD | 11 ++++++----- aports/temp/modemmanager/APKBUILD | 11 ++++++----- 3 files changed, 19 insertions(+), 16 deletions(-) diff --git a/aports/temp/libcamera/APKBUILD b/aports/temp/libcamera/APKBUILD index f7785b2..8e25f13 100644 --- a/aports/temp/libcamera/APKBUILD +++ b/aports/temp/libcamera/APKBUILD @@ -84,12 +84,13 @@ builddir="$srcdir/$pkgname-v$_pkgver" # manual strip because ipa .sign files depend on the file contents- have to re-sign after strip options="!strip !check" # fp6-img: this package only ever runs on the FP6 (4x Cortex-A520 + 4x -# Cortex-A720), so tune for it. Spelled -march/-mtune, not -mcpu: GCC 16 -# expands -mcpu=cortex-a720 into a +sve-bitperm token that binutils 2.45 -# rejects at the assembler. +nosve+nomemtag: the FP6 exposes neither SVE nor -# MTE (no sve/mte hwcaps), and without them the code would SIGILL. -export CFLAGS="$CFLAGS -march=armv9.2-a+nosve+nomemtag -mtune=cortex-a720" -export CXXFLAGS="$CXXFLAGS -march=armv9.2-a+nosve+nomemtag -mtune=cortex-a720" +# Cortex-A720), so build for it. armv8.6-a is the highest level whose +# mandatory set the phone exposes (8.7 would assume WFxT, 9.x SVE2; neither +# is in its hwcaps); +fp16fml+aes+sha3+sm4 are the optional extensions it +# has, the same set `gcc -march=native` derives on the phone. One spelling +# for gcc and clang, and the same string the kernel aport uses. +export CFLAGS="$CFLAGS -march=armv8.6-a+fp16fml+aes+sha3+sm4 -mtune=cortex-a720" +export CXXFLAGS="$CXXFLAGS -march=armv8.6-a+fp16fml+aes+sha3+sm4 -mtune=cortex-a720" case "$CARCH" in arm*|aarch64) diff --git a/aports/temp/libqmi/APKBUILD b/aports/temp/libqmi/APKBUILD index 520c5d9..bb04704 100644 --- a/aports/temp/libqmi/APKBUILD +++ b/aports/temp/libqmi/APKBUILD @@ -28,11 +28,12 @@ makedepends=" # failure), same as modemmanager; build under plain qemu. options="!pmb:crossdirect" # fp6-img: this package only ever runs on the FP6 (4x Cortex-A520 + 4x -# Cortex-A720), so tune for it. Spelled -march/-mtune, not -mcpu: GCC 16 -# expands -mcpu=cortex-a720 into a +sve-bitperm token that binutils 2.45 -# rejects at the assembler. +nosve+nomemtag: the FP6 exposes neither SVE nor -# MTE (no sve/mte hwcaps), and without them the code would SIGILL. -export CFLAGS="$CFLAGS -march=armv9.2-a+nosve+nomemtag -mtune=cortex-a720" +# Cortex-A720), so build for it. armv8.6-a is the highest level whose +# mandatory set the phone exposes (8.7 would assume WFxT, 9.x SVE2; neither +# is in its hwcaps); +fp16fml+aes+sha3+sm4 are the optional extensions it +# has, the same set `gcc -march=native` derives on the phone. One spelling +# for gcc and clang, and the same string the kernel aport uses. +export CFLAGS="$CFLAGS -march=armv8.6-a+fp16fml+aes+sha3+sm4 -mtune=cortex-a720" subpackages=" $pkgname-dev $pkgname-doc diff --git a/aports/temp/modemmanager/APKBUILD b/aports/temp/modemmanager/APKBUILD index 0891e6b..569d1e6 100644 --- a/aports/temp/modemmanager/APKBUILD +++ b/aports/temp/modemmanager/APKBUILD @@ -43,11 +43,12 @@ checkdepends="glib-dev py3-gobject3 py3-dbus" # on-device validation is the real test. options="!pmb:crossdirect !check" # fp6-img: this package only ever runs on the FP6 (4x Cortex-A520 + 4x -# Cortex-A720), so tune for it. Spelled -march/-mtune, not -mcpu: GCC 16 -# expands -mcpu=cortex-a720 into a +sve-bitperm token that binutils 2.45 -# rejects at the assembler. +nosve+nomemtag: the FP6 exposes neither SVE nor -# MTE (no sve/mte hwcaps), and without them the code would SIGILL. -export CFLAGS="$CFLAGS -march=armv9.2-a+nosve+nomemtag -mtune=cortex-a720" +# Cortex-A720), so build for it. armv8.6-a is the highest level whose +# mandatory set the phone exposes (8.7 would assume WFxT, 9.x SVE2; neither +# is in its hwcaps); +fp16fml+aes+sha3+sm4 are the optional extensions it +# has, the same set `gcc -march=native` derives on the phone. One spelling +# for gcc and clang, and the same string the kernel aport uses. +export CFLAGS="$CFLAGS -march=armv8.6-a+fp16fml+aes+sha3+sm4 -mtune=cortex-a720" subpackages=" $pkgname-lang $pkgname-doc From ed18cce7d0d2627057c8bd81b2c488d2a292d7b2 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Fri, 11 Sep 2026 13:03:22 +0200 Subject: [PATCH 3/3] fp6-vendor-blobs 1-r3: extract from the active slot, and stop pinning a hash on a signed image Two field units got no fingerprint sensor from fingerprintd 0.2.3's manifest: its focal64 line pinned the sha256 of one Android build's trustlet (16.82.0, the dev phone's), and Fairphone re-signs that trustlet every release, so the pin matches exactly one of the six builds seen. A user on 16.100.0 had to edit the manifest by hand; another ended up with a file QTEE refuses. The extractor now tries the active slot's partitions first (androidboot.slot_suffix from the kernel command line): for a signed image only the running TZ's own slot is guaranteed to load. An mbn line may give '-' instead of a hash, which means structural verification only: ELF64 header, every segment present at the size its program header declares, page-aligned offsets, a sane total. The loader in TZ verifies the signature and the per-segment hashes itself and refuses a damaged or foreign image (one flipped byte -> ERROR_ELF_SIGNATURE_ERROR, measured), so the whole-image hash added fragility and no protection. A real sha256 is still honoured, and the sha256 of what was installed is logged either way. --refresh re-derives mbn dests even when a file exists, replacing it only with an image that verifies; consumers call it from post-install/post-upgrade so a fresh 'apk add' needs no reboot and a hand-placed or wrongly pinned trustlet is replaced on the next upgrade. Verified on the dev phone (busybox): malformed inputs are refused with a reason (missing, truncated or oversize segment; non-ELF, ELF32 or short mdt; a garbage offset), both slots reassemble to the known-good hash, a foreign file survives a plain run and is replaced by --refresh, a failed refresh keeps the old file, pins still work, and the real post-upgrade path re-derived the installed trustlet with the daemon restarting on it. Record: fp6 repo journal/blobs/ and journal/fingerprint/, 2026-09-11. --- aports/device/fp6-vendor-blobs/APKBUILD | 13 +- .../fp6-vendor-blobs/fp6-vendor-blobs-extract | 113 ++++++++++++++---- 2 files changed, 100 insertions(+), 26 deletions(-) diff --git a/aports/device/fp6-vendor-blobs/APKBUILD b/aports/device/fp6-vendor-blobs/APKBUILD index 0a6d8c8..a03b09b 100644 --- a/aports/device/fp6-vendor-blobs/APKBUILD +++ b/aports/device/fp6-vendor-blobs/APKBUILD @@ -10,11 +10,18 @@ # their post-install/post-upgrade should also run # /usr/lib/fp6-vendor-blobs/extract --if-device so a package upgrade that # drops a previously-shipped blob restores the file immediately instead of -# at the next boot. First consumer: soc-fairphone-fp6-audio (aw88261 acf). +# at the next boot (--refresh in addition for an mbn consumer: it re-derives +# the trustlet from the active slot even if a file is present). Consumers: +# soc-fairphone-fp6-audio (aw88261 acf), fingerprintd (focal64 trustlet). +# +# 1-r3 (2026-09-11): active slot first, an unpinned ('-') structurally +# verified mode for mbn, --refresh -- after two field units got no +# fingerprint sensor from a whole-image hash pin that can only match one +# Android build (fp6 repo journal/blobs/, journal/fingerprint/). maintainer="Jorijn van der Graaf " pkgname=fp6-vendor-blobs pkgver=1 -pkgrel=2 +pkgrel=3 pkgdesc="On-device extraction of vendor blobs from the stock Android partitions" url="https://forgejo.catcrafts.net/Catcrafts/fp6-img" arch="noarch" @@ -55,7 +62,7 @@ package() { } sha512sums=" -2caafdedf93e103516834a1f815dd828ecee66c82d569e4a925ccc6bd6ac75d6778290adb02db69538af3bb6ad36cee5c13c8afba2c722a4c4550efe761ba8a0 fp6-vendor-blobs-extract +e9618b4a1ccbe0913a608b32a1a9e87337e3da79a3d7ccc7f49292ae9aaa9406d2ea9817d63b3cfff2c4346b80dd2476c10725f4f0f95c859acd17b22ec03a16 fp6-vendor-blobs-extract b4c290095d9f39515378dfef08de720ce49324210342aa13c131dfce1103785e796e6f821f0c659671a4c44b46f466ce0e03f11f216fdcdee2a99db5e7970800 fp6-vendor-blobs.service 9e79dd0aed13f11a71282aa24b2a26331e85c105e25ab0c0fed6189b8c300769a5f4308b18b91d9855868d658ad3a57c03e26c9b11bd27fd5e03f9a5decbbd6a fp6-vendor-blobs.preset " diff --git a/aports/device/fp6-vendor-blobs/fp6-vendor-blobs-extract b/aports/device/fp6-vendor-blobs/fp6-vendor-blobs-extract index 1289b21..7f5b940 100644 --- a/aports/device/fp6-vendor-blobs/fp6-vendor-blobs-extract +++ b/aports/device/fp6-vendor-blobs/fp6-vendor-blobs-extract @@ -10,9 +10,15 @@ # processed in sorted order; '#' comments and blank lines ignored: # # file -# mbn +# mbn # rebind # +# Partition lists are tried in the order written, except that on an A/B +# device the ACTIVE slot's partitions (androidboot.slot_suffix in +# /proc/cmdline) come first: the other slot may hold a different Android +# build, and for a signed image only the active slot's copy is guaranteed to +# match the TZ that is running. +# # file: mount the first available listed partition READ-ONLY (ext4 also # gets -o noload - never a byte written to the stock partitions, not # even a journal replay), copy to , verify the @@ -28,9 +34,19 @@ # Reassembly is therefore not a concatenation: segments are page aligned # but not contiguous, gaps stay zero, and two segments may share an offset # (focal64 has two such pairs), so they are written in index order and the -# later one wins. Same guarantees as file: the sha256 is of the reassembled -# image, a mismatch tries the next partition, and an unverified image is -# never installed. +# later one wins. With a real sha256 the guarantees are file's: the hash is +# of the reassembled image, a mismatch tries the next partition, an +# unverified image is never installed. With '-' the image is verified +# STRUCTURALLY instead - ELF64 header, every segment present at the size +# its program header declares, page-aligned offsets, sane total - and not +# against a pinned hash. That is the right mode for an OEM-signed trustlet: +# the OEM re-signs it every Android release, so one whole-image hash matches +# exactly one build (six builds, six hashes, one trustlet: fp6 repo +# journal/fingerprint/ 2026-09-07..11, two field units failed on the pin), +# while the loader in TZ verifies the signature and the per-segment hashes +# itself and refuses a damaged or foreign image (one flipped byte -> +# ERROR_ELF_SIGNATURE_ERROR, measured 2026-09-03). The sha256 of what was +# installed is logged either way. # rebind: if this fragment's run extracted at least one file, unbind and # re-probe on so the consuming driver picks the file up # in the same boot. Unconditional on purpose: a still-bound consumer may @@ -48,6 +64,13 @@ # --if-device: exit 0 quietly when no stock super partition is visible # (apk post-install scripts run inside build/CI chroots too; on images # built there the first-boot service does the real extraction). +# --refresh: re-derive every mbn dest even if it exists, replacing it only +# with an image that verifies (a failed refresh leaves the old file). For +# the consumer's post-install/post-upgrade: a fresh 'apk add' gets its +# trustlet without a reboot, and a trustlet that was hand-placed or pinned +# to another build is replaced by the active slot's on the next upgrade. +# file dests are still left alone: re-copying the acf would rebind the +# sound card on every upgrade for nothing. MANIFEST_DIR=/usr/share/fp6-vendor-blobs/manifest.d SUPER=/dev/disk/by-partlabel/super @@ -55,6 +78,10 @@ MNT= MNT_PART= CREATED= TRIED_MAPPING= +IF_DEVICE= +REFRESH= +# "a" or "b" on an A/B device (androidboot.slot_suffix=_a), else empty +ACTIVE_SLOT=$(tr ' ' '\n' /dev/null | sed -n 's/^androidboot\.slot_suffix=_\([ab]\)$/\1/p' | head -n1) log() { echo "fp6-vendor-blobs: $*"; } @@ -114,10 +141,26 @@ mount_part() { MNT_PART=$1 } +# The listed partitions, space separated, the active slot's first. +order_parts() { # + first= rest= + for p in $(echo "$1" | tr ',' ' '); do + if [ -n "$ACTIVE_SLOT" ] && [ "${p%_$ACTIVE_SLOT}" != "$p" ]; then + first="$first $p" + else + rest="$rest $p" + fi + done + echo "$first $rest" +} + # Little-endian scalars out of an ELF header. aarch64 is little endian and so # is the image, so od's host order is the right one. u64() { od -An -tu8 -j "$2" -N 8 "$1" | tr -d ' '; } u16() { od -An -tu2 -j "$2" -N 2 "$1" | tr -d ' '; } +u8() { od -An -tu1 -j "$2" -N 1 "$1" | tr -d ' '; } +hex4() { od -An -tx1 -N 4 "$1" | tr -d ' \n'; } +fsize() { stat -c %s "$1"; } # Reassemble /.mdt + .b0N into a flat image at . Mirrors # utilities/ta-analysis/reassemble.py in the fp6 bring-up repo, which is where @@ -130,9 +173,18 @@ reassemble() { # mdir=$1 mname=$2 mout=$3 mdt="$mdir/$mname.mdt" [ -f "$mdt" ] || return 1 + # Structure first, before anything is written: an ELF64 header whose + # program header table fits in the .mdt, and for every segment with + # contents a .b0N file of exactly the declared size at a page-aligned + # offset. This is the whole verification when the manifest pins no hash; + # the loader's own signature check does the rest. + [ "$(hex4 "$mdt")" = 7f454c46 ] || { log "$mname.mdt: not an ELF image"; return 1; } + [ "$(u8 "$mdt" 4)" = 2 ] || { log "$mname.mdt: not ELF64"; return 1; } phoff=$(u64 "$mdt" 32) phentsize=$(u16 "$mdt" 54) phnum=$(u16 "$mdt" 56) [ -n "$phoff" ] && [ -n "$phentsize" ] && [ -n "$phnum" ] || return 1 - [ "$phnum" -gt 0 ] 2>/dev/null || return 1 + [ "$phentsize" -eq 56 ] 2>/dev/null || { log "$mname.mdt: phentsize $phentsize"; return 1; } + [ "$phnum" -gt 0 ] 2>/dev/null && [ "$phnum" -le 64 ] || { log "$mname.mdt: phnum $phnum"; return 1; } + [ "$(fsize "$mdt")" -ge $((phoff + phnum * phentsize)) ] || { log "$mname.mdt: shorter than its program header table"; return 1; } # The image is as long as the furthest segment reaches; everything no # segment covers stays zero. @@ -142,11 +194,21 @@ reassemble() { # pfsz=$(u64 "$mdt" $((o + 32))) if [ "$pfsz" -gt 0 ]; then poff=$(u64 "$mdt" $((o + 8))) + seg=$(printf '%s/%s.b%02d' "$mdir" "$mname" "$i") + [ -f "$seg" ] || { log "$mname: segment $i missing"; return 1; } + [ "$(fsize "$seg")" -eq "$pfsz" ] || { log "$mname: segment $i is $(fsize "$seg") bytes, header says $pfsz"; return 1; } + # dd seeks in whole blocks, which is only correct because + # every p_offset in these images is page aligned. Refuse + # rather than silently misplace a segment if that changes. + [ $((poff % 4096)) -eq 0 ] || { log "$mname: segment $i offset $poff is not page aligned"; return 1; } [ $((poff + pfsz)) -gt "$total" ] && total=$((poff + pfsz)) fi i=$((i + 1)) done - [ "$total" -gt 0 ] || return 1 + [ "$total" -gt 0 ] || { log "$mname: no segment has contents"; return 1; } + # an order of magnitude above any TA; a garbage p_offset would otherwise + # make a sparse multi-GiB file that then gets hashed + [ "$total" -le $((64 * 1024 * 1024)) ] || { log "$mname: image would be $total bytes"; return 1; } : > "$mout" || return 1 truncate -s "$total" "$mout" || return 1 @@ -157,14 +219,6 @@ reassemble() { # if [ "$pfsz" -gt 0 ]; then poff=$(u64 "$mdt" $((o + 8))) seg=$(printf '%s/%s.b%02d' "$mdir" "$mname" "$i") - [ -f "$seg" ] || { log "$mname: segment $i missing"; return 1; } - # dd seeks in whole blocks, which is only correct because - # every p_offset in these images is page aligned. Refuse - # rather than silently misplace a segment if that changes. - [ $((poff % 4096)) -eq 0 ] || { - log "$mname: segment $i offset $poff is not page aligned" - return 1 - } dd if="$seg" of="$mout" bs=4096 seek=$((poff / 4096)) \ conv=notrunc 2>/dev/null || return 1 fi @@ -173,9 +227,9 @@ reassemble() { # return 0 } -extract_mbn() { # +extract_mbn() { # parts=$1 rdir=$2 rname=$3 dest=$4 want=$5 - for part in $(echo "$parts" | tr ',' ' '); do + for part in $(order_parts "$parts"); do mount_part "$part" || { log "$part: not mountable, trying next"; continue; } [ -f "$MNT/$rdir/$rname.mdt" ] || { log "$part: no $rdir/$rname.mdt, trying next"; continue; } tmp="$dest.fp6-extract.$$" @@ -186,21 +240,26 @@ extract_mbn() { # continue fi got=$(sha256sum "$tmp" | awk '{print $1}') - if [ "$got" != "$want" ]; then + if [ "$want" != - ] && [ "$got" != "$want" ]; then rm -f "$tmp" log "$part:$rdir/$rname sha256 $got != expected, trying next" continue fi chmod 644 "$tmp" && mv "$tmp" "$dest" || { rm -f "$tmp"; fail "installing $dest failed"; } - log "reassembled $part:$rdir/$rname.{mdt,b0N} -> $dest" + log "reassembled $part:$rdir/$rname.{mdt,b0N} -> $dest (sha256 $got)" return 0 done - fail "no listed partition ($parts) yields $rname with sha256 $want - $dest NOT installed" + kept= + [ -e "$dest" ] && kept=" (the existing file is left in place)" + if [ "$want" = - ]; then + fail "no listed partition ($parts) yields a well-formed $rname - $dest NOT installed$kept" + fi + fail "no listed partition ($parts) yields $rname with sha256 $want - $dest NOT installed$kept" } extract() { # parts=$1 src=$2 dest=$3 want=$4 - for part in $(echo "$parts" | tr ',' ' '); do + for part in $(order_parts "$parts"); do mount_part "$part" || { log "$part: not mountable, trying next"; continue; } [ -f "$MNT/$src" ] || { log "$part: no $src, trying next"; continue; } tmp="$dest.fp6-extract.$$" @@ -239,7 +298,14 @@ rebind_all() { # done } -if [ "${1:-}" = --if-device ] && [ ! -b "$SUPER" ]; then +for arg in "$@"; do + case "$arg" in + --if-device) IF_DEVICE=1 ;; + --refresh) REFRESH=1 ;; + *) fail "unknown option '$arg'" ;; + esac +done +if [ -n "$IF_DEVICE" ] && [ ! -b "$SUPER" ]; then log "no stock super partition visible (build chroot?), nothing to do" exit 0 fi @@ -253,11 +319,12 @@ for f in "$MANIFEST_DIR"/*.manifest; do while read -r kind a b c d e; do case "$kind" in file) [ -e "$c" ] || missing=1 ;; - mbn) [ -e "$d" ] || missing=1 ;; + mbn) [ -e "$d" ] && [ -z "$REFRESH" ] || missing=1 ;; esac done < "$f" done [ -z "$missing" ] && exit 0 +[ -n "$ACTIVE_SLOT" ] && log "active slot $ACTIVE_SLOT" for f in "$MANIFEST_DIR"/*.manifest; do [ -e "$f" ] || continue @@ -274,7 +341,7 @@ for f in "$MANIFEST_DIR"/*.manifest; do ;; mbn) [ -n "$e" ] || fail "$f: malformed mbn line" - [ -e "$d" ] && continue + [ -e "$d" ] && [ -z "$REFRESH" ] && continue extract_mbn "$a" "$b" "$c" "$d" "$e"