From 31a12d9b8dfa83db82b9be1d70e595c20702ef1c Mon Sep 17 00:00:00 2001 From: Daniel Rossier Date: Fri, 25 Sep 2026 15:00:33 +0200 Subject: [PATCH] usr-linux: deploy the user space where the running root is The usr apps reached the target only indirectly: a usr-linux bbappend (headed "EDGE-M1 Factory Capsule", the edge-m1 e1c capsule policy) replaced usr-linux:do_deploy with an injection INTO rootfs.cpio at build time, whatever the ramfs, and the deploy extracted that cpio onto p2. With IB_RAMFS_SOURCE = "initrd" (static ramfs pivoting to p2) the apps reached p2 only if nothing had regenerated rootfs.cpio since the last bsp-linux build (`build.sh rootfs-linux` then `deploy.sh bsp-linux` gave a card without them), and `deploy.sh usr-linux` never reached the card. Generic model, aligned on infrabase: bbappend removed, and usr-linux:do_deploy puts the apps in ONE place, chosen by IB_RAMFS_SOURCE: - "rootfs": baked INTO rootfs.cpio (extract, sudo rsync, re-pack). bsp-linux:do_prepare_initrd pulls it into the build, before gzipping the cpio into the ITB; it depends on rootfs-linux:do_build (never on the boot media) and runs after usr-linux:do_build. - anything else: copied onto p2 after rootfs-linux:do_deploy, with sudo rsync and check=True (p2 is root-owned), verdin skipped like rootfs-linux:do_deploy; bsp-linux:do_deploy pulls it, deploy-only, no `after do_build`, so a deploy never rebuilds usr and linux. do_prepare_initrd otherwise depends on rootfs-linux:do_build, as in infrabase. Validated with `bitbake -g bsp-linux` (build and deploy graphs) with IB_RAMFS_SOURCE forced to "rootfs" and to "initrd". --- .../recipes-bsp/linux/bsp-linux_1.0.bb | 31 ++++-- .../recipes-rootfs/linux/rootfs-linux_1.0.bb | 3 +- .../recipes-usr/linux/usr-linux_1.0.bb | 99 +++++++++++++++---- .../recipes-usr/linux/usr-linux_1.0.bbappend | 47 --------- 4 files changed, 103 insertions(+), 77 deletions(-) delete mode 100644 build/meta-usr/recipes-usr/linux/usr-linux_1.0.bbappend diff --git a/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb b/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb index 83e6744..6101893 100644 --- a/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb +++ b/build/meta-bsp/recipes-bsp/linux/bsp-linux_1.0.bb @@ -89,7 +89,7 @@ do_itb () { # overwrite the (now meaningful) static initrd.cpio. do_prepare_initrd[nostamp] = "1" -do_prepare_initrd[depends] = "usr-linux:do_deploy" +do_prepare_initrd[depends] = "rootfs-linux:do_build" python do_prepare_initrd () { import hashlib @@ -149,16 +149,31 @@ addtask do_prepare_initrd before do_itb # Deploy everything # # Deploy is decoupled from the build: it writes the already-built artefacts -# onto the boot media WITHOUT recompiling. It pulls rootfs-linux:do_deploy -# (which extracts the final rootfs.cpio — apps baked in at build time — onto -# p2) and writes the .itb produced by do_itb during `build.sh -a` onto p1 -# (__do_platform_deploy). It does NOT pull do_build / do_itb / usr-linux's -# rootfs.cpio populate — those belong to `build.sh -a`. Workflow: -# edit -> build.sh -> deploy.sh. A deploy with no prior build fails clearly -# (missing rootfs.cpio / .itb) rather than silently rebuilding. +# onto the boot media WITHOUT recompiling. The rootfs is extracted onto p2 +# (rootfs-linux:do_deploy); when p2 is the running root (IB_RAMFS_SOURCE other +# than "rootfs") it also pulls usr-linux:do_deploy, which copies the usr apps +# (linux/usr/build/deploy) on top — see the anonymous function below. With +# the rootfs.cpio ramfs the apps travel inside the ITB instead. +# It then writes the .itb produced by do_itb during `build.sh -a` onto p1 +# (__do_platform_deploy). It does NOT pull do_build / do_itb / usr-linux: +# do_build — those belong to `build.sh -a`. Workflow: edit -> build.sh -> +# deploy.sh. A deploy with no prior build fails clearly (missing rootfs.cpio / +# usr build/deploy / .itb) rather than silently rebuilding. do_deploy[depends] = "filesystem:do_fs_check rootfs-linux:do_deploy" +# usr-linux:do_deploy goes where the running root is (see usr-linux_1.0.bb): +# with IB_RAMFS_SOURCE = "rootfs" it bakes the apps into rootfs.cpio, so it is +# part of the BUILD, before do_prepare_initrd gzips that cpio into the ITB; +# otherwise it copies them onto p2 and is part of the DEPLOY. + +python () { + if (d.getVar('IB_RAMFS_SOURCE') or "rootfs").strip() == "rootfs": + d.appendVarFlag('do_prepare_initrd', 'depends', ' usr-linux:do_deploy') + else: + d.appendVarFlag('do_deploy', 'depends', ' usr-linux:do_deploy') +} + do_deploy[nostamp] = "1" python do_deploy() { diff --git a/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb b/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb index d65f500..648e015 100644 --- a/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb +++ b/build/meta-rootfs/recipes-rootfs/linux/rootfs-linux_1.0.bb @@ -24,7 +24,8 @@ do_build[depends] = "${IB_ROOTFS_METHOD}:do_build" do_build[depends] += "linux:do_build" # do_deploy is a pure DEPLOY step: it extracts the already-built rootfs.cpio -# (apps baked in by usr-linux:do_deploy during the build) onto p2. It must +# onto p2 (usr apps already inside when IB_RAMFS_SOURCE = "rootfs", copied +# on top by usr-linux:do_deploy otherwise). It must # NOT pull ${IB_ROOTFS_METHOD}:do_build — that would rebuild buildroot on # every `deploy.sh`. The workflow is edit -> build.sh (produces rootfs.cpio) # -> deploy.sh; a deploy with no prior build fails clearly in 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 97c327a..efd69d6 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 @@ -27,42 +27,99 @@ do_unpack[depends] += "linux:do_build" do_deploy[depends] = "rootfs-linux:do_deploy" do_deploy[nostamp] = "1" -# Deploy the usr contents, i.e. the deploy/ dir, in the corresponding partition of the filesystem +# Deploy the usr contents, i.e. the deploy/ dir, where the running system will +# find them. That depends on what the kernel boots as its root, and the apps go +# to ONE place only: +# +# IB_RAMFS_SOURCE = "rootfs" - the embedded ramfs IS rootfs.cpio, so the apps +# are baked INTO rootfs.cpio (rsync into the extracted tree, then re-pack). +# bsp-linux:do_prepare_initrd pulls this task into the BUILD, before it +# gzips rootfs.cpio into the ITB, so here it must not touch the boot media: +# it depends on rootfs-linux:do_build instead of rootfs-linux:do_deploy and +# runs after do_build (anonymous function below). +# +# any other value (the static "initrd" ramfs, which pivots to p2) - the apps +# are copied onto the rootfs partition p2, on top of what +# rootfs-linux:do_deploy extracted. bsp-linux:do_deploy pulls this task, +# so a full deploy always carries the user space. +# +# Unset means "rootfs", the bsp.bbclass default. + +def usr_linux_ramfs_is_rootfs(d): + return (d.getVar('IB_RAMFS_SOURCE') or "rootfs").strip() == "rootfs" + +python () { + if usr_linux_ramfs_is_rootfs(d): + d.setVarFlag('do_deploy', 'depends', 'rootfs-linux:do_build') + bb.build.addtask('do_deploy', None, 'do_build', d) +} + python do_deploy() { import os - __do_fs_mount(d) - IB_USR_PATH = d.getVar('IB_USR_PATH') + deploy_src = os.path.join(IB_USR_PATH, "build", "deploy") + + if not os.path.isdir(deploy_src): + bb.fatal("The {} does not exist; please build usr first...".format(deploy_src)) + + if usr_linux_ramfs_is_rootfs(d): + IB_ROOTFS_PATH = d.getVar('IB_ROOTFS_PATH') + IB_PLATFORM = d.getVar('IB_PLATFORM') + + if not os.path.isfile(os.path.join(IB_ROOTFS_PATH, "board", IB_PLATFORM, "rootfs.cpio")): + bb.fatal("rootfs.cpio is missing; please build rootfs first...") + + # The extracted tree is root-owned (cpio -id), so rsync needs root to + # write into it while preserving mode bits and ownership. + + d.setVar('ROOTFS_FILENAME', 'rootfs') + __do_rootfs_mount(d) + utils_sudo(["rsync", "-a", "--keep-dirlinks", + deploy_src + "/", f"{IB_ROOTFS_PATH}/fs/"], check=True) + __do_rootfs_umount(d) + + bb.plain("usr deployed into rootfs.cpio (IB_RAMFS_SOURCE = rootfs)") + return + + # Same exception as rootfs-linux:do_deploy: verdin-imx8mp storage goes + # through the Tezi / HTTP recovery flow, there is no p2 to mount here. + + if d.getVar('IB_PLATFORM') == "verdin-imx8mp": + bb.plain("verdin-imx8mp: rootfs delivered via Tezi/HTTP, skipping usr partition deploy") + return + IB_FILESYSTEM_PATH = d.getVar('IB_FILESYSTEM_PATH') IB_ROOTFS_PARTITION = d.getVar('IB_ROOTFS_PARTITION') + rootfs_dst = os.path.join(IB_FILESYSTEM_PATH, IB_ROOTFS_PARTITION) - if not os.path.isdir(os.path.join(IB_USR_PATH, "build", "deploy")): - - __do_fs_umount(d) - bb.fatal("The {} does not exist; please build usr first...".format(IB_USR_PATH)) + __do_fs_mount(d) - if not os.path.isdir(os.path.join(IB_FILESYSTEM_PATH, IB_ROOTFS_PARTITION, "root")): + if not os.path.isdir(os.path.join(rootfs_dst, "root")): __do_fs_umount(d) bb.fatal("The root directory is not present in the second partition; please deploy rootfs...") + # p2 is written by rootfs-linux:do_deploy with `sudo cp -a`, so its tree + # is root-owned: the copy needs root too. --keep-dirlinks keeps the + # rootfs directory symlinks (e.g. /lib -> usr/lib) instead of replacing + # them with real directories. check=True: a failed copy must fail the + # deploy, not leave a rootfs silently without the apps. - # Copy as root: the rootfs cpio extraction (run via sudo) creates /root - # owned by root (0700), and bitbake runs unprivileged — a plain cp would - # be denied and silently drop the apps. check=True so a failure is no - # longer swallowed (the old os.system() ignored the return code, which is - # why the soo apps never reached the agency /root). - utils_sudo("cp -r {}/build/deploy/* {}/{}/".format(IB_USR_PATH, IB_FILESYSTEM_PATH, IB_ROOTFS_PARTITION), shell=True, check=True) - - __do_fs_umount(d) + try: + utils_sudo(["rsync", "-a", "--keep-dirlinks", + deploy_src + "/", rootfs_dst + "/"], check=True) + finally: + __do_fs_umount(d) } -# `after do_build` is required: do_deploy expects ${IB_USR_PATH}/build/deploy/ -# (populated by do_build) to exist. Without explicit ordering, bitbake -# schedules do_deploy as soon as its declared depends (rootfs-linux:do_deploy) -# are met — which can be before this recipe's own do_build has run. -addtask do_deploy after do_build +# No static `after do_build`: in the p2 case do_deploy is a pure deploy step +# (edit -> build.sh -> deploy.sh) that copies the already-built build/deploy/ +# and fails clearly when it is missing, instead of dragging usr-linux:do_build +# (and linux:do_build through do_unpack) into every deploy. The rootfs.cpio +# case, which runs inside the build, gets its ordering above. + +addtask do_deploy # Build extra components which is not in src/ directory like modules do_build:prepend () { diff --git a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bbappend b/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bbappend deleted file mode 100644 index de4db47..0000000 --- a/build/meta-usr/recipes-usr/linux/usr-linux_1.0.bbappend +++ /dev/null @@ -1,47 +0,0 @@ -# Copyright (c) 2025-2026 EDGEMTech SA -# Adapted for MICOFE - Copyright (c) 2026 REDS Institute, HEIG-VD - -SUMMARY = "EDGE-M1 Factory Capsule - User space applications for Linux" -DESCRIPTION = "All (Linux) user space custom applications which take place in the rootfs of Linux" -LICENSE = "GPLv2" - -inherit usr -inherit linux -inherit filesystem -inherit rootfs - -# This is a BUILD step, not a media deploy: it bakes the agency apps INTO -# rootfs.cpio (rsync into the cpio tree, then re-pack). That single rootfs -# image is then the source for both targets: -# - the embedded ramfs (gzip -> initrd.cpio.gz -> ITB) for -# IB_RAMFS_SOURCE="rootfs"; -# - the p2 ext4 partition, which rootfs-linux:do_deploy populates by -# extracting this same (apps-included) rootfs.cpio at DEPLOY time. -# So p2 gets the apps for free via the final rootfs.cpio — no separate -# media write here. Depends on buildroot (rootfs.cpio) + usr:do_build -# (the apps), NOT on rootfs-linux:do_deploy, so the build never touches -# the boot media. -do_deploy[depends] = "${IB_ROOTFS_METHOD}:do_build" - -python do_deploy() { - - import os - - IB_USR_PATH = d.getVar('IB_USR_PATH') - IB_ROOTFS_PATH = d.getVar('IB_ROOTFS_PATH') - IB_PLATFORM = d.getVar('IB_PLATFORM') - - deploy_src = os.path.join(IB_USR_PATH, "build", "deploy") - if not os.path.isdir(deploy_src): - bb.fatal("The {} does not exist; please build usr first...".format(IB_USR_PATH)) - if not os.path.isfile(os.path.join(IB_ROOTFS_PATH, "board", IB_PLATFORM, "rootfs.cpio")): - bb.fatal("rootfs.cpio is missing; please build rootfs first...") - - # The destination tree is root-owned (cpio -id), so rsync needs root to - # preserve mode bits / ownership while writing into it. - d.setVar('ROOTFS_FILENAME', 'rootfs') - __do_rootfs_mount(d) - utils_sudo(["rsync", "-a", "--keep-dirlinks", - deploy_src + "/", f"{IB_ROOTFS_PATH}/fs/"], check=True) - __do_rootfs_umount(d) -} \ No newline at end of file