diff mbox series

image_types: make oe_mkext234fs reproducible

Message ID a380db6d-e22b-4417-ac04-d32cc23c698a@elder-tomes.com
State Under Review
Headers show
Series image_types: make oe_mkext234fs reproducible | expand

Commit Message

Levi Shafter Aug. 8, 2026, 6:11 p.m. UTC
oe_mkext234fs() creates ext2/3/4 images with mke2fs and then runs
"fsck -pvfD". Unlike the ext4 partitions produced by wic
(scripts/lib/wic/partition.py), this direct IMAGE_CMD path embeds build
time into the result and is not reproducible:

  - mke2fs picks a random directory hash seed, and the fsck "-D" pass
    reorders every directory using it;
  - mke2fs and e2fsck stamp the superblock mkfs/write/last-check times
    with the wall clock.

When SOURCE_DATE_EPOCH is set, apply the same handling wic already uses
for its ext4 partitions:

  - export E2FSPROGS_FAKE_TIME and pass a deterministic "-E hash_seed"
    derived from SOURCE_DATE_EPOCH (reusing wic's namespace UUID);
  - after mkfs+fsck, normalize the superblock time fields with debugfs,
    since e2fsck stamps wtime/lastcheck with the current time even under
    E2FSPROGS_FAKE_TIME (debugfs takes epochs with a leading '@').

The filesystem UUID is still assigned by mke2fs and is left for the user
to pin (e.g. "-U ..." via EXTRA_IMAGECMD), matching wic where the UUID
comes from the .wks.

Verified with comparison via xdelta3, getfattr, find, debugfs, and
dumpe2fs between two custom builds using the patch against two custom
builds omitting the patch. Builds were successful, and no delta was
observed due to the usage of fsck.

[YOCTO #16110]

Signed-off-by: Levi Shafter <lshafter@elder-tomes.com>
---
Sponsor: 21SoftWare LLC

v2: Pass deterministic data instead of implementing fsck toggle
v1: https://lists.openembedded.org/g/openembedded-core/topic/117020092

 meta/classes-recipe/image_types.bbclass | 32
++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

 	fstype=$1
 	extra_imagecmd=""
@@ -81,6 +92,14 @@ oe_mkext234fs () {
 		extra_imagecmd=$@
 	fi

+	# For reproducible builds, make mke2fs/e2fsck deterministic: a fixed
time and
+	# directory hash seed (the fsck "-D" pass reorders directories using it).
+	# This mirrors what wic does for its ext4 partitions.
+	if [ -n "$SOURCE_DATE_EPOCH" ]; then
+		export E2FSPROGS_FAKE_TIME="$SOURCE_DATE_EPOCH"
+		extra_imagecmd="$extra_imagecmd -E
hash_seed=${REPRODUCIBLE_EXT234_HASH_SEED}"
+	fi
+
 	# If generating an empty image the size of the sparse block should be
large
 	# enough to allocate an ext4 filesystem using 4096 bytes per inode,
this is
 	# about 60K, so dd needs a minimum count of 60, with bs=1024 (bytes
per IO)
@@ -98,6 +117,19 @@ oe_mkext234fs () {
 	mkfs.$fstype -F $extra_imagecmd ${IMGDEPLOYDIR}/${IMAGE_NAME}.$fstype
-d ${IMAGE_ROOTFS}
 	# Error codes 0-3 indicate successfull operation of fsck (no errors or
errors corrected)
 	fsck.$fstype -pvfD ${IMGDEPLOYDIR}/${IMAGE_NAME}.$fstype || [ $? -le 3 ]
+
+	# e2fsck stamps the superblock write/last-check times with the current
time
+	# even under E2FSPROGS_FAKE_TIME, so normalize every superblock time field
+	if [ -n "$SOURCE_DATE_EPOCH" ]; then
+		printf '%s\n' \
+			"set_super_value mkfs_time @$SOURCE_DATE_EPOCH" \
+			"set_super_value wtime @$SOURCE_DATE_EPOCH" \
+			"set_super_value lastcheck @$SOURCE_DATE_EPOCH" \
+			"set_super_value mtime @0" \
+			"set_super_value first_error_time @0" \
+			"set_super_value last_error_time @0" \
+			| debugfs -w -f - ${IMGDEPLOYDIR}/${IMAGE_NAME}.$fstype
+	fi
 }

 IMAGE_CMD:ext2 = "oe_mkext234fs ext2 ${EXTRA_IMAGECMD}"
diff mbox series

Patch

diff --git a/meta/classes-recipe/image_types.bbclass
b/meta/classes-recipe/image_types.bbclass
index ca13729225..c0b3a78013 100644
--- a/meta/classes-recipe/image_types.bbclass
+++ b/meta/classes-recipe/image_types.bbclass
@@ -72,6 +72,17 @@  IMAGE_CMD:jffs2 = "mkfs.jffs2 --root=${IMAGE_ROOTFS}
--faketime --output=${IMGDE

 IMAGE_CMD:cramfs = "mkfs.cramfs ${IMAGE_ROOTFS}
${IMGDEPLOYDIR}/${IMAGE_NAME}.cramfs ${EXTRA_IMAGECMD}"

+# Derive a deterministic directory hash seed from SOURCE_DATE_EPOCH so
+# reproducible builds get stable ext2/3/4 directory indexes
+def oe_ext234_hash_seed(d):
+    sde = d.getVar('SOURCE_DATE_EPOCH')
+    if not sde:
+        return ''
+    import uuid
+    return
str(uuid.uuid5(uuid.UUID('e7429877-e7b3-4a68-a5c9-2f2fdf33d460'), sde))
+
+REPRODUCIBLE_EXT234_HASH_SEED ?= "${@oe_ext234_hash_seed(d)}"
+
 oe_mkext234fs () {