diff --git a/aports/device/fp6-vendor-blobs/APKBUILD b/aports/device/fp6-vendor-blobs/APKBUILD new file mode 100644 index 0000000..b1e6d74 --- /dev/null +++ b/aports/device/fp6-vendor-blobs/APKBUILD @@ -0,0 +1,47 @@ +# First-boot, on-device extraction of proprietary blobs from the stock +# Android partitions, so the image never has to ship or distribute them. +# pmOS installs flash only boot+userdata: the stock vendor/dsp partitions +# stay on every installed unit, and the device duplicates files it already +# lawfully contains, for its own operation. Design, legal frame and the +# on-phone verification: fp6 repo journal/blobs/. +# +# Consumers depend on this package and install a manifest fragment into +# /usr/share/fp6-vendor-blobs/manifest.d/ (syntax in the extract script); +# 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). +maintainer="Jorijn van der Graaf " +pkgname=fp6-vendor-blobs +pkgver=1 +pkgrel=0 +pkgdesc="On-device extraction of vendor blobs from the stock Android partitions" +url="https://forgejo.catcrafts.net/Catcrafts/fp6-img" +arch="noarch" +license="MIT" +# fallback mapper for when the initramfs didn't map the dynamic partitions +depends="make-dynpart-mappings" +options="!check" +source=" + fp6-vendor-blobs-extract + fp6-vendor-blobs.service +" + +package() { + install -Dm755 "$srcdir"/fp6-vendor-blobs-extract \ + "$pkgdir"/usr/lib/fp6-vendor-blobs/extract + install -Dm644 "$srcdir"/fp6-vendor-blobs.service \ + "$pkgdir"/usr/lib/systemd/system/fp6-vendor-blobs.service + # enabled unconditionally: the unit is a fast no-op once every manifest + # dest exists, and blobs appearing on first boot must not depend on a + # manual systemctl enable + mkdir -p "$pkgdir"/etc/systemd/system/multi-user.target.wants + ln -s /usr/lib/systemd/system/fp6-vendor-blobs.service \ + "$pkgdir"/etc/systemd/system/multi-user.target.wants/fp6-vendor-blobs.service + mkdir -p "$pkgdir"/usr/share/fp6-vendor-blobs/manifest.d +} + +sha512sums=" +a71b2c86f980734d0aae6e135b26272fe52ae5603050bea52f55f7e74c8bd98c62b6194047711248d4fef40851792adc47d77c40016d99a0d68ecd2459894b80 fp6-vendor-blobs-extract +e2c03e5016f1848c57e6160ce432530b181ff5d34a5aaf1822f0ffa5fe71d16691657ada4a5789c4d4209e14e83f43aab072d01f644b07b93648f91e2a7bb0ca fp6-vendor-blobs.service +" diff --git a/aports/device/fp6-vendor-blobs/fp6-vendor-blobs-extract b/aports/device/fp6-vendor-blobs/fp6-vendor-blobs-extract new file mode 100644 index 0000000..4239697 --- /dev/null +++ b/aports/device/fp6-vendor-blobs/fp6-vendor-blobs-extract @@ -0,0 +1,190 @@ +#!/bin/sh -u +# fp6-vendor-blobs-extract - copy proprietary blobs out of the stock Android +# partitions instead of distributing them. pmOS installs flash only +# boot+userdata, so every installed FP6 still carries the stock vendor/dsp +# partitions: the device duplicates files it already contains, for its own +# operation - nothing is distributed by us or anyone else. Design, legal +# frame and on-phone verification: fp6 repo journal/blobs/. +# +# Manifest fragments: /usr/share/fp6-vendor-blobs/manifest.d/*.manifest, +# processed in sorted order; '#' comments and blank lines ignored: +# +# file +# rebind +# +# 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 +# sha256. A missing source or a hash mismatch tries the next listed +# partition; no verified copy on any of them fails the run loudly - an +# unverified blob is never installed and a missing one never silently +# skipped. Dests that already exist are left alone (no hashing: a +# deliberately replaced file stays). +# 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 +# have already failed a deferred firmware request that is never retried +# (aw88261 binds on i2c probe but requests the ACF only at card init), +# so only a fresh probe with the file present is a known-good state. +# All of a fragment's devices are unbound first, then re-probed, so a +# shared sound card re-forms once instead of bouncing per device. +# +# Partitions resolve via /dev/mapper (the pmOS initramfs maps the dynamic +# partitions in super on every boot), then /dev/disk/by-partlabel (raw +# partitions like dsp_a), then one make-dynpart-mappings fallback run; +# mappings that run creates are removed again at the end. +# +# --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). + +MANIFEST_DIR=/usr/share/fp6-vendor-blobs/manifest.d +SUPER=/dev/disk/by-partlabel/super +MNT= +MNT_PART= +CREATED= +TRIED_MAPPING= + +log() { echo "fp6-vendor-blobs: $*"; } + +unmount_cur() { + if [ -n "$MNT" ]; then + umount "$MNT" 2>/dev/null + rmdir "$MNT" 2>/dev/null + MNT= MNT_PART= + fi +} + +cleanup() { + unmount_cur + for name in $CREATED; do + dmsetup remove "$name" 2>/dev/null || true + done + CREATED= +} + +fail() { + echo "fp6-vendor-blobs: ERROR: $*" >&2 + cleanup + exit 1 +} + +# resolve a partition name to a block device +part_dev() { + [ -b "/dev/mapper/$1" ] && { echo "/dev/mapper/$1"; return 0; } + [ -b "/dev/disk/by-partlabel/$1" ] && { echo "/dev/disk/by-partlabel/$1"; return 0; } + if [ -z "$TRIED_MAPPING" ] && [ -b "$SUPER" ]; then + TRIED_MAPPING=1 + before=$(dmsetup ls 2>/dev/null | awk '{print $1}') + make-dynpart-mappings "$SUPER" 0 2>/dev/null + after=$(dmsetup ls 2>/dev/null | awk '{print $1}') + for name in $after; do + case " $before " in *" $name "*) ;; *) CREATED="$CREATED $name" ;; esac + done + [ -n "$CREATED" ] && log "mapped dynamic partitions:$CREATED" + [ -b "/dev/mapper/$1" ] && { echo "/dev/mapper/$1"; return 0; } + fi + return 1 +} + +mount_part() { + [ "$MNT_PART" = "$1" ] && return 0 + unmount_cur + dev=$(part_dev "$1") || return 1 + dir=$(mktemp -d /run/fp6-vendor-blobs.XXXXXX) || fail "mktemp failed" + # noload succeeds on any ext4 and correctly fails on non-ext4, where + # plain ro cannot write anyway (erofs); a dirty ext4 is never replayed + if ! mount -o ro,noload "$dev" "$dir" 2>/dev/null && \ + ! mount -o ro "$dev" "$dir" 2>/dev/null; then + rmdir "$dir" 2>/dev/null + return 1 + fi + MNT=$dir + MNT_PART=$1 +} + +extract() { # + parts=$1 src=$2 dest=$3 want=$4 + for part in $(echo "$parts" | tr ',' ' '); 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.$$" + mkdir -p "${dest%/*}" || fail "cannot create ${dest%/*}" + cp "$MNT/$src" "$tmp" || { rm -f "$tmp"; fail "copying $part:$src failed"; } + got=$(sha256sum "$tmp" | awk '{print $1}') + if [ "$got" != "$want" ]; then + rm -f "$tmp" + log "$part:$src sha256 $got != expected, trying next" + continue + fi + chmod 644 "$tmp" && mv "$tmp" "$dest" || { rm -f "$tmp"; fail "installing $dest failed"; } + log "extracted $part:$src -> $dest" + return 0 + done + fail "no listed partition ($parts) yields $src with sha256 $want - $dest NOT installed" +} + +rebind_all() { # + for rb in $1; do + bus=${rb%%/*} dev=${rb#*/} + [ -e "/sys/bus/$bus/devices/$dev/driver" ] || continue + if echo "$dev" > "/sys/bus/$bus/devices/$dev/driver/unbind" 2>/dev/null; then + log "unbound $bus $dev" + fi + done + for rb in $1; do + bus=${rb%%/*} dev=${rb#*/} + if [ ! -e "/sys/bus/$bus/devices/$dev" ]; then + log "rebind: no $dev on bus $bus (yet) - its driver will probe on its own" + elif echo "$dev" > "/sys/bus/$bus/drivers_probe" 2>/dev/null; then + log "re-probed $bus $dev" + else + log "rebind: drivers_probe of $dev failed (driver not loaded yet?)" + fi + done +} + +if [ "${1:-}" = --if-device ] && [ ! -b "$SUPER" ]; then + log "no stock super partition visible (build chroot?), nothing to do" + exit 0 +fi + +[ -d "$MANIFEST_DIR" ] || exit 0 + +# fast path for every boot after the first: all dests already present +missing= +for f in "$MANIFEST_DIR"/*.manifest; do + [ -e "$f" ] || continue + while read -r kind _ _ dest _; do + [ "$kind" = file ] && [ ! -e "$dest" ] && missing=1 + done < "$f" +done +[ -z "$missing" ] && exit 0 + +for f in "$MANIFEST_DIR"/*.manifest; do + [ -e "$f" ] || continue + extracted= + rebinds= + while read -r kind a b c d; do + case "$kind" in + ''|'#'*) ;; + file) + [ -n "$d" ] || fail "$f: malformed file line" + [ -e "$c" ] && continue + extract "$a" "$b" "$c" "$d"