From 72d25fb38a1d89786221bf03b967bbdeb1555238 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:17:38 +0300 Subject: [PATCH 1/2] sysupgrade: carry the settings on the split NOR layout after the hand-off Since #2648 every NOR rootfs write hands PID 1 to the RAM root, and the second phase's release_old_root lazily unmounts /mnt -- the old root and the jffs2 overlay under it. do_update_firmware_split then looked for rootfs_data in /proc/mounts, found nothing, took the camera for one without settings and staged an empty blob, still printing "Settings staged". The first boot honours an empty blob by erasing the overlay: every split NOR upgrade since 2026-10-21 came back on defaults (reported on a gk7205v300, nightly-20261022 and -20261023). The first phase now records that the overlay was mounted (split_overlay_mounted, exported into the RAM phase). Finding it gone, the RAM phase mounts rootfs_data read-only and copies the settings from that, as rewrite_ubifs does on UBI. Nothing holds the partition by then. If that mount fails, the run stops before the write: a reboot with nothing written, not an upgrade to defaults. -n and an overlay that was never mounted (a tmpfs one) still stage an empty blob. Tests: test_sysupgrade.sh (336 checks pass) gains the handed-off RAM phase on the split layout: the settings are carried, an unmountable overlay refuses before writing, -n stays empty, and the first phase records the mount. Without this change the first of those reproduces the field result exactly (blob length 000000000000). QEMU (qemu-hisilicon hi3516ev300, split layout, OpenIPC U-Boot): a boot script in the jffs2 overlay writes a marker and runs sysupgrade -z --archive with the hi3516ev300 split package; both runs hand PID 1 off. - master's sysupgrade: "Clearing settings after an upgrade...", and the script (part of the overlay) is gone after the reboot; - this one: "Restoring settings carried over by an upgrade...", and the script runs again from the carried overlay. Not run on hardware. --- .github/scripts/test_sysupgrade.sh | 61 +++++++++++++++++++++++++++++ general/overlay/usr/sbin/sysupgrade | 36 +++++++++++++++-- 2 files changed, 93 insertions(+), 4 deletions(-) diff --git a/.github/scripts/test_sysupgrade.sh b/.github/scripts/test_sysupgrade.sh index 589967886..1b16c5f1c 100755 --- a/.github/scripts/test_sysupgrade.sh +++ b/.github/scripts/test_sysupgrade.sh @@ -388,6 +388,14 @@ case " $* " in rm -rf "$target"; mkdir -p "$target" ;; esac exit 0 ;; + # On the split layout the handed-off RAM phase mounts the jffs2 overlay + # read-only to copy the settings out, since its own mount has gone + # with the old root. It finds the marker the camera had. + *" -t jffs2 "*) + [ "1" = "$STUB_JFFS2_RO_FAIL" ] && exit 255 + echo "mount jffs2 ro $target" >> "$FLASH_LOG" + mkdir -p "$target/etc"; echo settings > "$target/etc/marker" + exit 0 ;; esac case "${STUB_MOUNT:-ok}" in ok) @@ -3382,6 +3390,59 @@ else bad "split layout -n, rc=$RC len='$(blob_len 2 "$OVL" 0x50000)' out='$OUT'" fi +# Handed off: the RAM phase runs as PID 1, release_old_root has let go of the +# old root and the overlay's mount with it, so /proc/mounts no longer shows +# rootfs_data. The settings are still on it, and must still be carried: an +# empty blob here is the camera starting from defaults after every NOR upgrade +# since the hand-off (#2648). +split_handed_off() { + split_env split + printf 'tmpfs %s/tmp tmpfs rw 0 0\n' "$SB" > "$SB/proc/mounts" + RUN_ENV="$RUN_ENV _ramfs_phase=1 _handoff=1 split_overlay_mounted=1 image_combined=1 update_kernel=1 update_rootfs=1 model=gk7205v200 skip_soc=1 skip_ver=1 root_on_flash=1 ram_root_shipped=1" + tar -xzf "$SB/tmp/fw.tgz" -C "$SB/tmp" +} +split_handed_off +run +u=$(logged_at "umount -l /mnt"); m=$(logged_at "mount jffs2 ro") +if [ "$(flash_at 2 0x50000 0x50000 8)" = "$FW_HEAD" ] && + blob_files 2 "$OVL" 0x50000 | grep -q 'etc/marker' && + [ -n "$u" ] && [ -n "$m" ] && [ "$u" -lt "$m" ] && rebooted; then + ok "split layout, handed off: the overlay is mounted read-only and the settings carried" +else + bad "split layout handed off must carry the settings, len='$(blob_len 2 "$OVL" 0x50000)' log='$(cat "$SB/tmp/flash.log")' out='$OUT'" +fi + +# The overlay cannot be read: refused before the write, not upgraded to defaults. +split_handed_off +STUB_JFFS2_RO_FAIL=1 run +if printf '%s' "$OUT" | grep -q "Could not read the settings" && + [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && ! printf '%s' "$OUT" | grep -q "Firmware updated"; then + ok "split layout, handed off: an overlay that will not mount stops the run, nothing written" +else + bad "split layout handed off, unreadable overlay must refuse, rc=$RC out='$OUT'" +fi + +# -n handed off: nothing to carry, nothing mounted. +split_handed_off +RUN_ENV="$RUN_ENV clear_overlay=1" +run +if [ "$(blob_len 2 "$OVL" 0x50000)" = 000000000000 ] && ! grep -q "mount jffs2" "$SB/tmp/flash.log"; then + ok "split layout, handed off, -n: an empty blob, the overlay left unmounted" +else + bad "split layout handed off -n, len='$(blob_len 2 "$OVL" 0x50000)' out='$OUT'" +fi + +# The first phase records that there are settings, for the RAM phase to look for. +split_env split +STUB_PIVOT_RC=0 +run -z --archive="$SB/tmp/fw.tgz" +if grep -q "^kill -QUIT 1" "$SB/tmp/flash.log" && + grep -q "split_overlay_mounted=.1" "$SB/ram/sysupgrade.env" 2>/dev/null; then + ok "split layout: the hand-off carries that the overlay was mounted" +else + bad "split layout hand-off must record the mounted overlay, log='$(cat "$SB/tmp/flash.log")' env='$(grep overlay "$SB/ram/sysupgrade.env" 2>/dev/null)' out='$OUT'" +fi + # From fixed slots: the image runs on from the kernel slot into the rootfs one, # and the settings land in the old rootfs, where the new rootfs_data starts. split_env fixed diff --git a/general/overlay/usr/sbin/sysupgrade b/general/overlay/usr/sbin/sysupgrade index c8b602cbc..0854e7d41 100755 --- a/general/overlay/usr/sbin/sysupgrade +++ b/general/overlay/usr/sbin/sysupgrade @@ -1,6 +1,6 @@ #!/bin/sh # OpenIPC.org | 2025 -scr_version=1.0.76 +scr_version=1.0.77 args="$@" LOCK_FILE=/tmp/sysupgrade.lock @@ -341,6 +341,10 @@ check_combined_fits() { [ $(($(nor_split_base) + $(stat -c %s "$x"))) -le "$ovl" ] || die "$x runs past the end of its squashfs ($(printf 0x%x "$ovl")): not a firmware.bin as the build packs it. Nothing was written." blob=$(nor_split_blob_size) + # Whether there are settings to carry: the hand-off takes the overlay's + # mount away before the write (release_old_root), so the RAM phase cannot + # tell from /proc/mounts any more. See do_update_firmware_split. + [ -n "$(nor_split_overlay_mp)" ] && split_overlay_mounted=1 [ $((ovl + blob)) -le "$end" ] || die "$x and the settings do not fit the flash: the image ends at $(printf 0x%x "$ovl"), the settings need $((blob / 1024)) KB, the flash ends at $(printf 0x%x "$end"). Nothing was written; -n upgrades without the settings." } @@ -914,11 +918,16 @@ nor_split_url() { fi } +# Where the jffs2 overlay on rootfs_data is mounted, if it is. +nor_split_overlay_mp() { + awk -v d="$(get_device rootfs_data | sed 's|/dev/mtd|/dev/mtdblock|')" '$1 == d { print $2; exit }' /proc/mounts +} + # The settings blob this run will write, in bytes: the header, the gzipped tar # of the overlay, whole 64 KiB blocks; one block for -n. nor_split_blob_size() { local mp n=0 - mp=$(awk -v d="$(get_device rootfs_data | sed 's|/dev/mtd|/dev/mtdblock|')" '$1 == d { print $2; exit }' /proc/mounts) + mp=$(nor_split_overlay_mp) if [ "1" != "$clear_overlay" ] && [ -n "$mp" ]; then n=$(tar -C "$mp" -cf - . 2>&3 | gzip | wc -c) fi @@ -944,10 +953,29 @@ do_update_firmware_split() { [ -n "$end" ] || die "The flash layout is neither the split one nor fixed slots this upgrade can turn into it. Nothing was written." rm -f "$blob" "$tgz" quiesce_overlay - mp=$(awk -v d="$(get_device rootfs_data | sed 's|/dev/mtd|/dev/mtdblock|')" '$1 == d { print $2; exit }' /proc/mounts) + mp=$(nor_split_overlay_mp) + # Handed off, the overlay is no longer mounted: release_old_root let go of + # everything behind /mnt, the overlay with it. Reading /proc/mounts alone + # then took the camera for one without settings and staged an empty blob, + # which the first boot honours by starting from defaults -- every NOR + # upgrade since the hand-off (#2648). Nothing holds rootfs_data any more, + # so mount it read-only here and copy the settings from that, as + # rewrite_ubifs does on UBI. Refusing costs a reboot with nothing written; + # going on would cost the settings. + if [ "1" != "$clear_overlay" ] && [ -z "$mp" ] && [ "1" = "$split_overlay_mounted" ]; then + mp=/tmp/overlay.old + mkdir -p "$mp" && + mount -t jffs2 -o ro "$(get_device rootfs_data | sed 's|/dev/mtd|/dev/mtdblock|')" "$mp" 2>&3 || + die "Could not read the settings to carry them over. Nothing was written; -n upgrades without them." + overlay_mounted_here=1 + fi if [ "1" != "$clear_overlay" ] && [ -n "$mp" ]; then tar -C "$mp" -cf - . 2>&3 | gzip > "$tgz" || die "Could not copy the settings into RAM. Nothing was written; -n upgrades without them." + if [ "1" = "$overlay_mounted_here" ]; then + busybox umount "$mp" 2>&3 + rmdir "$mp" 2>&3 + fi else # Length 0: the first boot erases the overlay and restores nothing. : > "$tgz" @@ -2584,7 +2612,7 @@ enter_ramfs() { export live_flash_dirty root_on_flash flash_touched services_stopped # remote_update: verify_rootfs branches on it for an unmountable rootfs. # lock_owned: die() needs it to know the lock is ours to remove. - export remote_update lock_owned + export remote_update lock_owned split_overlay_mounted # wdog_userspace_owned: watchdog_keep must not claim a device nobody was # petting, and the scan behind that answer needs a /proc this pivot is about # to move and a majestic it is about to leave behind /mnt. From 66d2101ef16f26f800e4ce30d3e1c9d96a9ce856 Mon Sep 17 00:00:00 2001 From: Dmitry Ilyin <6576495+widgetii@users.noreply.github.com> Date: Sat, 24 Oct 2026 21:49:29 +0300 Subject: [PATCH 2/2] sysupgrade: refuse the split write when the settings mount will not let go The temporary read-only mount of rootfs_data has to be released before the flash under it is rewritten; if busybox umount fails, die before the write instead of going on. The jffs2 mount stub now serves files from a directory named for the device it is asked to mount, so the handed-off test checks that the settings come from rootfs_data (mtdblock5) rather than from the stub. A new test injects the unmount failure: refused, nothing written. test_sysupgrade.sh: 337 checks pass. --- .github/scripts/test_sysupgrade.sh | 28 ++++++++++++++++++++++++---- general/overlay/usr/sbin/sysupgrade | 5 ++++- 2 files changed, 28 insertions(+), 5 deletions(-) diff --git a/.github/scripts/test_sysupgrade.sh b/.github/scripts/test_sysupgrade.sh index 1b16c5f1c..497e3210f 100755 --- a/.github/scripts/test_sysupgrade.sh +++ b/.github/scripts/test_sysupgrade.sh @@ -343,6 +343,7 @@ case "$applet" in kill|ubirmvol|ubirsvol|ubimkvol) echo "$applet $*" >> "$FLASH_LOG" ;; umount) echo "$applet $*" >> "$FLASH_LOG" + case "$*" in *overlay.old*) [ "1" = "$STUB_OVL_UMOUNT_FAIL" ] && exit 1 ;; esac exit "${STUB_BB_UMOUNT_RC:-0}" ;; # The applet list enter_ramfs links into the RAM root, and the RAM-root # reservation counts. Silent unless a test asks for one. @@ -391,10 +392,14 @@ case " $* " in # On the split layout the handed-off RAM phase mounts the jffs2 overlay # read-only to copy the settings out, since its own mount has gone # with the old root. It finds the marker the camera had. + # The files come from STUB_JFFS2_DIR/, so a mount of any + # other device finds nothing and fails, as a wrong partition would. *" -t jffs2 "*) [ "1" = "$STUB_JFFS2_RO_FAIL" ] && exit 255 - echo "mount jffs2 ro $target" >> "$FLASH_LOG" - mkdir -p "$target/etc"; echo settings > "$target/etc/marker" + dev=${@: -2:1}; src=$STUB_JFFS2_DIR/${dev##*/} + [ -d "$src" ] || exit 255 + echo "mount jffs2 ro $dev $target" >> "$FLASH_LOG" + mkdir -p "$target" && cp -a "$src/." "$target/" exit 0 ;; esac case "${STUB_MOUNT:-ok}" in @@ -3398,14 +3403,19 @@ fi split_handed_off() { split_env split printf 'tmpfs %s/tmp tmpfs rw 0 0\n' "$SB" > "$SB/proc/mounts" - RUN_ENV="$RUN_ENV _ramfs_phase=1 _handoff=1 split_overlay_mounted=1 image_combined=1 update_kernel=1 update_rootfs=1 model=gk7205v200 skip_soc=1 skip_ver=1 root_on_flash=1 ram_root_shipped=1" + # What is on rootfs_data (mtdblock5): the settings, unlike anything the + # stub could make up. + rm -rf "$SB/jffs2"; mkdir -p "$SB/jffs2/mtdblock5/etc" + echo "from-rootfs_data" > "$SB/jffs2/mtdblock5/etc/carried" + RUN_ENV="$RUN_ENV STUB_JFFS2_DIR=$SB/jffs2 _ramfs_phase=1 _handoff=1 split_overlay_mounted=1 image_combined=1 update_kernel=1 update_rootfs=1 model=gk7205v200 skip_soc=1 skip_ver=1 root_on_flash=1 ram_root_shipped=1" tar -xzf "$SB/tmp/fw.tgz" -C "$SB/tmp" } split_handed_off run u=$(logged_at "umount -l /mnt"); m=$(logged_at "mount jffs2 ro") if [ "$(flash_at 2 0x50000 0x50000 8)" = "$FW_HEAD" ] && - blob_files 2 "$OVL" 0x50000 | grep -q 'etc/marker' && + blob_files 2 "$OVL" 0x50000 | grep -q 'etc/carried' && + grep -q "mount jffs2 ro .*mtdblock5 " "$SB/tmp/flash.log" && [ -n "$u" ] && [ -n "$m" ] && [ "$u" -lt "$m" ] && rebooted; then ok "split layout, handed off: the overlay is mounted read-only and the settings carried" else @@ -3422,6 +3432,16 @@ else bad "split layout handed off, unreadable overlay must refuse, rc=$RC out='$OUT'" fi +# The copy is made but the partition will not let go: refused before the write. +split_handed_off +STUB_OVL_UMOUNT_FAIL=1 run +if printf '%s' "$OUT" | grep -q "Could not release the settings partition" && + [ "$(xxd -p -l 4 "$SB/dev/mtdblock2")" = 00000000 ] && ! printf '%s' "$OUT" | grep -q "Firmware updated"; then + ok "split layout, handed off: a settings mount that will not unmount stops the run, nothing written" +else + bad "split layout handed off, stuck settings mount must refuse, rc=$RC out='$OUT'" +fi + # -n handed off: nothing to carry, nothing mounted. split_handed_off RUN_ENV="$RUN_ENV clear_overlay=1" diff --git a/general/overlay/usr/sbin/sysupgrade b/general/overlay/usr/sbin/sysupgrade index 0854e7d41..be71d24e6 100755 --- a/general/overlay/usr/sbin/sysupgrade +++ b/general/overlay/usr/sbin/sysupgrade @@ -972,8 +972,11 @@ do_update_firmware_split() { if [ "1" != "$clear_overlay" ] && [ -n "$mp" ]; then tar -C "$mp" -cf - . 2>&3 | gzip > "$tgz" || die "Could not copy the settings into RAM. Nothing was written; -n upgrades without them." + # Read-only, but still a filesystem on the flash about to be + # rewritten: let go of it before the write, or do not write. if [ "1" = "$overlay_mounted_here" ]; then - busybox umount "$mp" 2>&3 + busybox umount "$mp" 2>&3 || + die "Could not release the settings partition after copying it. Nothing was written." rmdir "$mp" 2>&3 fi else