From 53dabfa9fa29051b694ff5891f4870e76c0ff152 Mon Sep 17 00:00:00 2001 From: Daniel Rossier Date: Fri, 25 Sep 2026 17:00:10 +0200 Subject: [PATCH 1/2] build: fix two deploy races on a fresh tree, and usr do_clean Three independent defects, all generic, found while aligning the pos course trees onto this build system. do_deploy_boot_chain ran before the storage image existed. With IB_FIRST_STAGE_FROM_CARD the virt64 chain dd's U-Boot into the raw area of sdcard.img, but nothing ordered the task after the image creation: on a fresh tree it failed with "no storage image ... run init_storage.sh first", and with bsp-linux it raced filesystem:do_fs_check. It now depends on filesystem:do_fs_check, which is a no-op when the image exists. Partitioning waited a fixed 2 s for the loop partition nodes. Inside the build container they come from the host's udev and regularly take longer: mkfs.fat then ran on a missing /dev/loopNp1, failed silently (no check), and the next deploy died on "wrong fs type" when mounting p1. The nodes are now polled (up to 20 s, fatal otherwise) and both mkfs calls check their exit status. usr.bbclass defines do_clean in Python, but the usr recipes appended shell to it; bitbake pastes the append verbatim, so `-c clean` failed on a SyntaxError. The shell bodies now live in named shell functions called from a Python append. Running them for real surfaced two latent bugs, fixed here too: the module clean ran make against a kernel tree that does not exist before the first linux build, and (where present) the lvgl clean of usr-so3 wiped WORKDIR/temp, which holds the running task's own fifo. Also: dbuild.sh now forwards IB_FORCE_ATTACH and IB_PARTITION_LAYOUT to the container. The attach guard tells the user to set IB_FORCE_ATTACH=1, which never reached bitbake under dbuild.sh. --- build/meta-bsp/classes/bsp.bbclass | 5 +++- .../classes/fs_arm_common.bbclass | 25 ++++++++++++++----- .../recipes-usr/linux/usr-linux_1.0.bb | 14 ++++++++--- .../recipes-usr/lvgl/usr-linux_1.0.bbappend | 9 ++++++- scripts/dbuild.sh | 10 ++++++++ 5 files changed, 52 insertions(+), 11 deletions(-) diff --git a/build/meta-bsp/classes/bsp.bbclass b/build/meta-bsp/classes/bsp.bbclass index 8b7c57a51..0e5638bef 100644 --- a/build/meta-bsp/classes/bsp.bbclass +++ b/build/meta-bsp/classes/bsp.bbclass @@ -166,7 +166,10 @@ inherit filesystem # `deploy.sh ` without a prior build still pulls them through sstate). do_deploy_boot_chain[nostamp] = "1" -do_deploy_boot_chain[depends] = "uboot:do_build" +# filesystem:do_fs_check because a chain may write its first stage into the +# raw area of the card (bsp_virt64.inc, IB_FIRST_STAGE_FROM_CARD): on a fresh +# tree the storage image must exist before, not be created by do_deploy after. +do_deploy_boot_chain[depends] = "uboot:do_build filesystem:do_fs_check" python () { extra = [] diff --git a/build/meta-filesystem/classes/fs_arm_common.bbclass b/build/meta-filesystem/classes/fs_arm_common.bbclass index 30ddea85c..ff2eb7c24 100644 --- a/build/meta-filesystem/classes/fs_arm_common.bbclass +++ b/build/meta-filesystem/classes/fs_arm_common.bbclass @@ -27,6 +27,7 @@ IB_PARTITION_LAYOUT ?= "${@'ab' if d.getVar('IB_ZEPHYR_BOOT_APP') else 'rootfs'} def __platform_init_storage(d): import os import subprocess + import time IB_STORAGE_MODE = d.getVar('IB_STORAGE_MODE') IB_ROOTFS_SIZE = d.getVar('IB_ROOTFS_SIZE') @@ -128,14 +129,26 @@ def __platform_init_storage(d): print("Waiting ...") - # TODO: use ionotify(7) - # Give a chance to the real SD-card to be sync'd - time.sleep(2) - if devname[-1].isdigit(): devname += "p" - utils_sudo(["mkfs.fat", "-F32", "-a", "-v", "-n", "boot", f"/dev/{devname}1"]) + # Wait for the partition nodes rather than a fixed delay. They are created + # asynchronously by the host's udev, and inside the build container + # (dbuild.sh bind-mounts the host /dev) that regularly takes longer than + # the 2 s this used to sleep: mkfs then ran on a node that did not exist + # yet, failed, and the card was left with an unformatted p1 that only + # showed up as a failed mount in the next deploy. + parts = ("1", "2", "3") if layout == "ab" else ("1", "2") + for _ in range(100): + if all(os.path.exists(f"/dev/{devname}{n}") for n in parts): + break + time.sleep(0.2) + else: + bb.fatal(f"/dev/{devname}{{{','.join(parts)}}} did not appear after " + "partitioning /dev/" + devname.rstrip("p")) + + utils_sudo(["mkfs.fat", "-F32", "-a", "-v", "-n", "boot", f"/dev/{devname}1"], + check=True) if layout == "ab": # p2 and p3 are raw slots. A filesystem on them would be a filesystem @@ -146,7 +159,7 @@ def __platform_init_storage(d): utils_sudo(["dd", "if=/dev/zero", f"of=/dev/{devname}{part}", "bs=1M", "count=1", "conv=fsync"]) else: - utils_sudo(["mkfs.ext4", "-L", "rootfs1", f"/dev/{devname}2"]) + utils_sudo(["mkfs.ext4", "-L", "rootfs1", f"/dev/{devname}2"], check=True) if IB_STORAGE_MODE == "soft": utils_sudo(["losetup", "-D"]) diff --git a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb index dfbede236..df5fadcd9 100644 --- a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb +++ b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb @@ -146,10 +146,14 @@ do_install_apps () { usr_do_install_file_root "${IB_TARGET}/src/modules/*.ko" } -do_clean:append () { +# usr.bbclass defines do_clean in Python, and a shell :append is pasted +# verbatim into that Python function, so `-c clean` died on a SyntaxError. +# The shell stays shell, in its own function, called from a Python append. +usr_linux_clean () { - # Clean the modules - if [ -d ${IB_TARGET}/src/modules ]; then + # Clean the modules — only when there is a kernel tree to clean them + # against; before the first linux build there is nothing to clean. + if [ -d ${IB_TARGET}/src/modules ] && [ -d ${IB_LINUX_PATH} ]; then make -C ${IB_LINUX_PATH} M=${IB_TARGET}/src/modules clean fi @@ -166,3 +170,7 @@ do_clean:append () { # Clean the user space apps rm -rf ${IB_TARGET}/build } + +python do_clean:append () { + bb.build.exec_func('usr_linux_clean', d) +} diff --git a/build/meta-usr/recipes-usr/lvgl/usr-linux_1.0.bbappend b/build/meta-usr/recipes-usr/lvgl/usr-linux_1.0.bbappend index fc61b713f..f518015c1 100644 --- a/build/meta-usr/recipes-usr/lvgl/usr-linux_1.0.bbappend +++ b/build/meta-usr/recipes-usr/lvgl/usr-linux_1.0.bbappend @@ -60,7 +60,10 @@ do_install_apps:append () { fi } -do_clean:append () { +# usr.bbclass defines do_clean in Python, and a shell :append is pasted +# verbatim into that Python function, so `-c clean` died on a SyntaxError. +# The shell stays shell, in its own function, called from a Python append. +usr_linux_clean_lvgl () { if echo ":${OVERRIDES}:" | grep -q ":lvgl"; then @@ -76,3 +79,7 @@ do_clean:append () { rm -rf ${WORKDIR}/git fi } + +python do_clean:append () { + bb.build.exec_func('usr_linux_clean_lvgl', d) +} diff --git a/scripts/dbuild.sh b/scripts/dbuild.sh index 1c4292ff3..29f58cc30 100755 --- a/scripts/dbuild.sh +++ b/scripts/dbuild.sh @@ -119,6 +119,16 @@ fi set -- -e IB_TREE="$IB_ROOT" -e IB_CWD="$cwd" "$@" +# Forward the per-invocation knobs env.sh lets through to bitbake +# (BB_ENV_PASSTHROUGH_ADDITIONS) when they are set on the host. Without it +# `IB_FORCE_ATTACH=1 dbuild.sh build.sh ` — what the attach guard +# itself advises — never reached the container, and the guard kept refusing. + +for _v in IB_FORCE_ATTACH IB_PARTITION_LAYOUT; do + eval "_val=\${$_v:-}" + [ -n "$_val" ] && set -- -e "$_v=$_val" "$@" +done + # Hardware deployment: make any IB_HTTP_DEPLOY_PATH feed directory # visible at its own path so `deploy.sh` can publish into it from inside # (IB_STORAGE_MODE=http). This serves the verdin-imx8mp TEZI flow; on the From f5d0b85056e4e056c8aac90a12d49a465a9b618d Mon Sep 17 00:00:00 2001 From: Daniel Rossier Date: Fri, 25 Sep 2026 18:46:34 +0200 Subject: [PATCH 2/2] usr-linux: drop the extracted rootfs when the copy into it fails In the IB_RAMFS_SOURCE = "rootfs" case, do_deploy extracts rootfs.cpio, rsyncs the user space into it and re-packs it. A failed rsync left the root-owned extracted tree behind, where the p2 path unmounts in a finally (raised in review of the edgem1 MR). Not a plain finally here: __do_rootfs_umount re-packs the tree INTO rootfs.cpio, so it would bake a half-copied user space into the archive. On failure the tree is dropped and rootfs.cpio is left as it was; the next deploy starts over from it. Verified in pos_student with IB_RAMFS_SOURCE = "rootfs": a normal deploy re-packs rootfs.cpio with the apps; a deploy made to fail (a file where the rootfs has a directory, rsync exit 23) fails the task, leaves rootfs.cpio byte-identical and removes the extracted tree. --- .../meta-usr/recipes-usr/linux/usr-linux_1.0.bb | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb index df5fadcd9..77a39b530 100644 --- a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb +++ b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bb @@ -74,8 +74,20 @@ python do_deploy() { d.setVar('ROOTFS_FILENAME', 'rootfs') __do_rootfs_mount(d) - utils_sudo(["rsync", "-a", "--keep-dirlinks", - deploy_src + "/", f"{IB_ROOTFS_PATH}/fs/"], check=True) + + # A failed copy must not leave the extracted, root-owned tree behind, + # as the p2 path below makes sure of for its mount. But not through a + # plain finally: __do_rootfs_umount re-packs the tree INTO rootfs.cpio, + # and a half-copied user space must not end up there. On failure the + # tree is dropped and rootfs.cpio stays as it was; the next deploy + # starts over from it. + try: + utils_sudo(["rsync", "-a", "--keep-dirlinks", + deploy_src + "/", f"{IB_ROOTFS_PATH}/fs/"], check=True) + except Exception: + utils_sudo(["rm", "-rf", os.path.join(d.getVar('WORKDIR'), "fs")]) + raise + __do_rootfs_umount(d) bb.plain("usr deployed into rootfs.cpio (IB_RAMFS_SOURCE = rootfs)")