[RFC,08/12] wic: rework image deploy logic to deploy under correct user

Message ID 20260218115827.3947145-9-felix.moessbauer@siemens.com
State New
Headers show
Series add support to build isar unprivileged | expand

Commit Message

MOESSBAUER, Felix Feb. 18, 2026, 11:58 a.m. UTC
We previously deployed the image file as root and then chowned the
deployed files to the calling user. Hereby the chown command itself
requires to be run under root, which is not possible on rootless.

As a preparation for rootless, we rework the deploy logic to deploy the
files under the calling user. For that, we deploy to a temporary
directory within workdir that is writeable from inside the chroot and
then copy out under the calling user.

Signed-off-by: Felix Moessbauer <felix.moessbauer@siemens.com>
---
 RECIPE-API-CHANGELOG.md                       | 12 +++++
 .../image-tools-extension.bbclass             | 11 +++++
 meta/classes-recipe/image.bbclass             | 10 +++-
 meta/classes-recipe/imagetypes.bbclass        | 47 +++++++++++--------
 meta/classes-recipe/imagetypes_wic.bbclass    | 10 ++--
 meta/classes-recipe/squashfs.bbclass          |  2 +-
 6 files changed, 66 insertions(+), 26 deletions(-)

Patch

diff --git a/RECIPE-API-CHANGELOG.md b/RECIPE-API-CHANGELOG.md
index bc40a403..f80630a0 100644
--- a/RECIPE-API-CHANGELOG.md
+++ b/RECIPE-API-CHANGELOG.md
@@ -978,3 +978,15 @@  specifies the rootfs path.
 Using these helpers instead of direct `sudo` invocations centralizes platform-specific
 privileged execution logic in `base.bbclass`. Direct use of `sudo` is discouraged
 in downstream layers.
+
+### Changes to image types
+
+The way different image types are handled has changed to be be compatible with
+rootless builds. For that, the deployment of images happens in two steps:
+
+1. generate the image in the `${IMAGE_STAGE_CHROOT}`
+2. the `imager_run` or `${SUDO_CHROOT}` command takes care of deploying the image
+   into the `${DEPLOY_DIR_IMAGE}`
+
+Conversion commands need to follow this strategy as well, but can read the image
+(prior to conversion) from `${IMAGE_FILE_CHROOT}`.
diff --git a/meta/classes-recipe/image-tools-extension.bbclass b/meta/classes-recipe/image-tools-extension.bbclass
index e88557f6..2eac3619 100644
--- a/meta/classes-recipe/image-tools-extension.bbclass
+++ b/meta/classes-recipe/image-tools-extension.bbclass
@@ -17,6 +17,17 @@  SCHROOT_MOUNTS = "${WORKDIR}:${PP_WORK} ${IMAGE_ROOTFS}:${PP_ROOTFS} ${DEPLOY_DI
 SCHROOT_MOUNTS += "${REPO_ISAR_DIR}/${DISTRO}:/isar-apt"
 
 imager_run() {
+    IMAGE_STAGE_DIR=$(dirname $IMAGE_STAGE_HOST)
+    create_chroot_parent_dir $IMAGE_STAGE_DIR
+    imager_run_${ISAR_CHROOT_MODE} "$@"
+
+    # copy locally deployed files with correct permissions to deploy dir
+    find $IMAGE_STAGE_DIR -type f -exec cp {} ${DEPLOY_DIR_IMAGE} \;
+    # on error keep the files for investigation
+    run_privileged rm -rf $IMAGE_STAGE_DIR
+}
+
+imager_run_schroot() {
     local_install="${@(d.getVar("INSTALL_%s" % d.getVar("BB_CURRENTTASK")) or '').strip()}"
     local_bom="${@(d.getVar("BOM_%s" % d.getVar("BB_CURRENTTASK")) or '').strip()}"
 
diff --git a/meta/classes-recipe/image.bbclass b/meta/classes-recipe/image.bbclass
index ca449ec5..e0e19adf 100644
--- a/meta/classes-recipe/image.bbclass
+++ b/meta/classes-recipe/image.bbclass
@@ -180,8 +180,14 @@  IMGCLASSES += "${IMAGE_CLASSES}"
 inherit ${IMGCLASSES}
 
 # convenience variables to be used by CMDs
+# Note, that the variables are only valid within the type specific task itself
+# but not in transitively called shell functions
 IMAGE_FILE_HOST = "${DEPLOY_DIR_IMAGE}/${IMAGE_FULLNAME}.${type}"
+# view (only for reading) the image in the deploy dir (useful for conversion commands)
 IMAGE_FILE_CHROOT = "${PP_DEPLOY}/${IMAGE_FULLNAME}.${type}"
+# staging location for copy-out (should only be written to from chroot)
+IMAGE_STAGE_HOST = "${WORKDIR}/deploy-image-${type}/${IMAGE_FULLNAME}.${type}"
+IMAGE_STAGE_CHROOT = "${PP_WORK}/deploy-image-${type}/${IMAGE_FULLNAME}.${type}"
 SUDO_CHROOT = "imager_run -d ${PP_ROOTFS} -u root --"
 
 # hook up IMAGE_CMD_*
@@ -262,8 +268,8 @@  python() {
         image_cmd = localdata.getVar('IMAGE_CMD:' + bt_clean)
         if image_cmd:
             localdata.setVar('type', bt)
+            cmds.append(localdata.expand('\tIMAGE_STAGE_HOST="${IMAGE_STAGE_HOST}"'))
             cmds.append(localdata.expand(image_cmd))
-            cmds.append(localdata.expand('\tsudo chown $(id -u):$(id -g) ${IMAGE_FILE_HOST}'))
         else:
             bb.fatal("No IMAGE_CMD for %s" % bt)
         vardeps.add('IMAGE_CMD:' + bt_clean)
@@ -292,8 +298,8 @@  python() {
                     localdata.setVar('type', t)
                     cmd = '\t' + localdata.getVar('CONVERSION_CMD:' + c)
                     if cmd not in cmds:
+                        cmds.append(localdata.expand('\tIMAGE_STAGE_HOST="${IMAGE_STAGE_HOST}"'))
                         cmds.append(cmd)
-                        cmds.append(localdata.expand('\tsudo chown $(id -u):$(id -g) ${IMAGE_FILE_HOST}.%s' % c))
                     vardeps.add('CONVERSION_CMD:' + c)
                     for dep in (localdata.getVar('CONVERSION_DEPS:' + c) or '').split():
                         conversion_install.add(dep)
diff --git a/meta/classes-recipe/imagetypes.bbclass b/meta/classes-recipe/imagetypes.bbclass
index f802c11c..78b89393 100644
--- a/meta/classes-recipe/imagetypes.bbclass
+++ b/meta/classes-recipe/imagetypes.bbclass
@@ -9,7 +9,7 @@  TAR_TRANSFORM = "--transform='s|rootfs|.|'"
 TAR_OPTIONS:append = " ${TAR_TRANSFORM}"
 IMAGE_CMD:tar() {
     ${SUDO_CHROOT} tar ${TAR_OPTIONS} -cvSf \
-                 ${IMAGE_FILE_CHROOT} --one-file-system -C ${PP} rootfs
+                 ${IMAGE_STAGE_CHROOT} --one-file-system -C ${PP} rootfs
 }
 
 # image type: ext4
@@ -38,10 +38,11 @@  do_image_ext4[prefuncs] = "set_mke2fs_args"
 IMAGE_CMD:ext4() {
     export E2FSPROGS_FAKE_TIME="${SOURCE_DATE_EPOCH}"
 
-    truncate -s ${ROOTFS_SIZE}K '${IMAGE_FILE_HOST}'
-
-    ${SUDO_CHROOT} /sbin/mke2fs ${MKE2FS_ARGS} \
-                -F -d '${PP_ROOTFS}' '${IMAGE_FILE_CHROOT}'
+    ${SUDO_CHROOT} /bin/bash -s <<'EOF'
+        set -e
+        truncate -s ${ROOTFS_SIZE}K '${IMAGE_STAGE_CHROOT}'
+        /sbin/mke2fs ${MKE2FS_ARGS} -F -d '${PP_ROOTFS}' '${IMAGE_STAGE_CHROOT}'
+EOF
 }
 
 # image type: cpio
@@ -49,10 +50,12 @@  IMAGER_INSTALL:cpio += "cpio"
 CPIO_IMAGE_FORMAT ?= "newc"
 
 IMAGE_CMD:cpio() {
-    ${SUDO_CHROOT} \
-        sh -c "cd ${PP_ROOTFS}; /usr/bin/find . | \
-               /usr/bin/cpio -H ${CPIO_IMAGE_FORMAT} -o > \
-               ${IMAGE_FILE_CHROOT}"
+    imager_run -p -d ${PP_WORK} -u root <<'EOIMAGER'
+        set -e
+        cd '${PP_ROOTFS}'; /usr/bin/find . | \
+            /usr/bin/cpio -H ${CPIO_IMAGE_FORMAT} -o > \
+               '${IMAGE_STAGE_CHROOT}'
+EOIMAGER
 }
 
 # image type: fit
@@ -72,8 +75,9 @@  IMAGE_CMD:fit() {
         die "FIT_IMAGE_SOURCE does not contain fitimage source file"
     fi
 
-    ${SUDO_CHROOT} /usr/bin/mkimage ${MKIMAGE_ARGS} \
-                -f '${PP_WORK}/${FIT_IMAGE_SOURCE}' '${IMAGE_FILE_CHROOT}'
+    ${SUDO_CHROOT} /usr/bin/mkimage \
+        ${MKIMAGE_ARGS} -f '${PP_WORK}/${FIT_IMAGE_SOURCE}' \
+        '${IMAGE_STAGE_CHROOT}'
 }
 IMAGE_CMD:fit[depends] = "${PN}:do_transform_template"
 
@@ -90,8 +94,9 @@  THIS_ISAR_CROSS_COMPILE := "${ISAR_CROSS_COMPILE}"
 ISAR_CROSS_COMPILE:armhf = "${@bb.utils.contains('IMAGE_BASETYPES', 'ubifs', '1', '${THIS_ISAR_CROSS_COMPILE}', d)}"
 
 IMAGE_CMD:ubifs() {
-    ${SUDO_CHROOT} /usr/sbin/mkfs.ubifs ${MKUBIFS_ARGS} \
-                -r '${PP_ROOTFS}' '${IMAGE_FILE_CHROOT}'
+    ${SUDO_CHROOT} /usr/sbin/mkfs.ubifs \
+        ${MKUBIFS_ARGS} -r '${PP_ROOTFS}' \
+        '${IMAGE_FILE_CHROOT}'
 }
 
 # image type: ubi
@@ -108,22 +113,26 @@  IMAGE_CMD:ubi() {
         die "UBINIZE_CFG does not contain ubinize config file."
     fi
 
-    ${SUDO_CHROOT} /usr/sbin/ubinize ${UBINIZE_ARGS} \
-                -o '${IMAGE_FILE_CHROOT}' '${PP_WORK}/${UBINIZE_CFG}'
+    ${SUDO_CHROOT} /usr/sbin/ubinize \
+        ${UBINIZE_ARGS} -o '${IMAGE_STAGE_CHROOT}' \
+        '${PP_WORK}/${UBINIZE_CFG}'
 }
 IMAGE_CMD:ubi[depends] = "${PN}:do_transform_template"
 
 # image conversions
 IMAGE_CONVERSIONS = "gz xz zst zck"
 
-CONVERSION_CMD:gz = "${SUDO_CHROOT} sh -c 'gzip -f -9 -n -c --rsyncable ${IMAGE_FILE_CHROOT} > ${IMAGE_FILE_CHROOT}.gz'"
+# image conversions
+IMAGE_CONVERSIONS = "gz xz zst zck"
+
+CONVERSION_CMD:gz = "${SUDO_CHROOT} sh -c 'gzip -f -9 -n -c --rsyncable ${IMAGE_FILE_CHROOT} > ${IMAGE_STAGE_CHROOT}.gz'"
 CONVERSION_DEPS:gz = "gzip"
 
-CONVERSION_CMD:xz = "${SUDO_CHROOT} sh -c 'xz -c ${XZ_DEFAULTS} ${IMAGE_FILE_CHROOT} > ${IMAGE_FILE_CHROOT}.xz'"
+CONVERSION_CMD:xz = "${SUDO_CHROOT} sh -c 'xz -c ${XZ_DEFAULTS} ${IMAGE_FILE_CHROOT} > ${IMAGE_STAGE_CHROOT}.xz'"
 CONVERSION_DEPS:xz = "xz-utils"
 
-CONVERSION_CMD:zst = "${SUDO_CHROOT} sh -c 'zstd -c --sparse ${ZSTD_DEFAULTS} ${IMAGE_FILE_CHROOT} > ${IMAGE_FILE_CHROOT}.zst'"
+CONVERSION_CMD:zst = "${SUDO_CHROOT} sh -c 'zstd -c --sparse ${ZSTD_DEFAULTS} ${IMAGE_FILE_CHROOT} > ${IMAGE_STAGE_CHROOT}.zst'"
 CONVERSION_DEPS:zst = "zstd"
 
-CONVERSION_CMD:zck = "${SUDO_CHROOT} sh -c 'cd $(dirname ${IMAGE_FILE_CHROOT}); zck ${ZCK_DEFAULTS} ${IMAGE_FILE_CHROOT}'"
+CONVERSION_CMD:zck = "${SUDO_CHROOT} sh -c 'cd $(dirname ${IMAGE_FILE_CHROOT}); zck ${ZCK_DEFAULTS} ${IMAGE_STAGE_CHROOT}'"
 CONVERSION_DEPS:zck = "zchunk"
diff --git a/meta/classes-recipe/imagetypes_wic.bbclass b/meta/classes-recipe/imagetypes_wic.bbclass
index 63974a3e..ebf3ce8e 100644
--- a/meta/classes-recipe/imagetypes_wic.bbclass
+++ b/meta/classes-recipe/imagetypes_wic.bbclass
@@ -145,6 +145,9 @@  check_for_wic_warnings() {
 
 do_image_wic[file-checksums] += "${WKS_FILE_CHECKSUM}"
 IMAGE_CMD:wic() {
+    # variable is type specific, hence capture here and
+    # forward to functions via export
+    export IMAGE_STAGE_CHROOT="${IMAGE_STAGE_CHROOT}"
     generate_wic_image
     check_for_wic_warnings
 }
@@ -181,20 +184,19 @@  generate_wic_image() {
             -e "${IMAGE_BASENAME}" ${WIC_CREATE_EXTRA_ARGS}
 
         WIC_DIRECT=$(ls -t -1 /tmp/${IMAGE_FULLNAME}.wic/*.direct | head -1)
-        mv -f ${WIC_DIRECT} ${PP_DEPLOY}/${IMAGE_FULLNAME}.wic
-        mv -f ${WIC_DIRECT}.bmap ${PP_DEPLOY}/${IMAGE_FULLNAME}.wic.bmap
+        mv -f ${WIC_DIRECT} $IMAGE_STAGE_CHROOT
+        mv -f ${WIC_DIRECT}.bmap $IMAGE_STAGE_CHROOT.bmap
         # deploy partition files if requested (ending with .p<x>)
         if [ "${WIC_DEPLOY_PARTITIONS}" -eq "1" ]; then
             # locate *.direct.p<x> partition files
             find "/tmp/${IMAGE_FULLNAME}.wic/" -type f -regextype sed -regex ".*\.direct.*\.p[0-9]\{1,\}" | while read f; do
                 suffix=$(basename $f | sed 's/.*\.direct\(.*\)/\1/')
-                mv -f ${f} ${PP_DEPLOY}/${IMAGE_FULLNAME}.wic${suffix}
+                mv -f ${f} $IMAGE_STAGE_CHROOT${suffix}
             done
         fi
 EOIMAGER
 
     run_privileged chown -R $(stat -c "%U" ${LAYERDIR_core}) ${LAYERDIR_core} ${LAYERDIR_isar} ${SCRIPTSDIR} || true
-    run_privileged chown -R $(id -u):$(id -g) "${DEPLOY_DIR_IMAGE}/${IMAGE_FULLNAME}.wic"*
     rm -rf ${IMAGE_ROOTFS}/../pseudo
 
     cat ${DEPLOY_DIR_IMAGE}/${IMAGE_FULLNAME}.manifest \
diff --git a/meta/classes-recipe/squashfs.bbclass b/meta/classes-recipe/squashfs.bbclass
index 9cd7ed3d..8330ffb5 100644
--- a/meta/classes-recipe/squashfs.bbclass
+++ b/meta/classes-recipe/squashfs.bbclass
@@ -42,6 +42,6 @@  IMAGE_CMD:squashfs[depends] = "${PN}:do_transform_template"
 IMAGE_CMD:squashfs[vardepsexclude] += "SQUASHFS_CREATION_LIMITS"
 IMAGE_CMD:squashfs() {
     ${SUDO_CHROOT} /bin/mksquashfs \
-        '${SQUASHFS_CONTENT}' '${IMAGE_FILE_CHROOT}' \
+        '${SQUASHFS_CONTENT}' '${IMAGE_STAGE_CHROOT}' \
         -noappend ${SQUASHFS_CREATION_LIMITS} ${SQUASHFS_CREATION_ARGS}
 }