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 () {
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}"