From 409f9d8fdbe0be590deb0137c3cf85970f20c133 Mon Sep 17 00:00:00 2001 From: Reflex Date: Tue, 11 Aug 2026 19:47:50 +0000 Subject: [PATCH 1/2] fix(mkfs): lay out the devbox rootfs for cheap offline grows The rootfs image is baked at one size and grown to the devbox's disk size by an offline resize2fs before first mount, on a layered block format where every block the grow writes becomes a new block. Drop sparse_super2. Its second backup superblock lives in the last block group; growing the filesystem relocates that backup and frees one block too many, leaving the group descriptor pointing at a block bitmap the allocator believes is free. The next workload to allocate that far writes user data over the bitmap. Use meta_bg instead of resize_inode. resize_inode reserves a contiguous GDT area that resize2fs rewrites in full on every grow, so a 64GiB image grown to 128GiB writes 1216 blocks; meta_bg reserves nothing and writes 89. Promote gdt_csum to metadata_csum. Both mark appended block groups uninitialized, so the grow never zeroes an inode table, but metadata_csum also checksums every metadata structure -- the only damage signal available on a filesystem carrying no journal. Its checksums seed from s_uuid, so the seed is re-derived once the real UUID is installed. Set 16 block groups per flex group. At the previous value of 1 each group kept its bitmaps and inode table at a fixed offset inside itself, which is what put them in the blast radius of the sparse_super2 relocation. Guard the resize inode creation behind its feature, matching mke2fs. Co-Authored-By: Claude Opus 5 --- CMake/Findphoton.cmake | 11 +-- .../photon-v0.6.17-ext4-rootfs-format.patch | 86 +++++++++++++++++++ .../photon-v0.6.17-ext4-uninit-bg.patch | 16 ---- 3 files changed, 92 insertions(+), 21 deletions(-) create mode 100644 CMake/patches/photon-v0.6.17-ext4-rootfs-format.patch delete mode 100644 CMake/patches/photon-v0.6.17-ext4-uninit-bg.patch diff --git a/CMake/Findphoton.cmake b/CMake/Findphoton.cmake index fcbbbead..c5cf966d 100644 --- a/CMake/Findphoton.cmake +++ b/CMake/Findphoton.cmake @@ -2,12 +2,13 @@ include(FetchContent) set(FETCHCONTENT_QUIET false) set(PHOTON_ENABLE_EXTFS ON) -# Runloop: patch Photon's ext4 mkfs to enable uninitialized block groups -# (gdt_csum). This lets an offline resize2fs grow of a devbox rootfs mark new -# inode tables uninitialized instead of zeroing them — a near-instant grow with -# minimal writes into the layered (COW) block format. +# Runloop: patch Photon's ext4 mkfs to lay out a devbox rootfs for the layered +# (COW) block format it lives on. The image is baked at one size and grown to +# the devbox's disk size by an offline resize2fs before first mount, so the +# feature set is chosen to make that grow write as few blocks as possible while +# leaving the result checksummed and correct. # Idempotent: skips if the patch is already applied (reverse-check succeeds). -set(_photon_mkfs_patch "${CMAKE_CURRENT_LIST_DIR}/patches/photon-v0.6.17-ext4-uninit-bg.patch") +set(_photon_mkfs_patch "${CMAKE_CURRENT_LIST_DIR}/patches/photon-v0.6.17-ext4-rootfs-format.patch") FetchContent_Declare( photon GIT_REPOSITORY https://github.com/alibaba/PhotonLibOS.git diff --git a/CMake/patches/photon-v0.6.17-ext4-rootfs-format.patch b/CMake/patches/photon-v0.6.17-ext4-rootfs-format.patch new file mode 100644 index 00000000..ae0991e7 --- /dev/null +++ b/CMake/patches/photon-v0.6.17-ext4-rootfs-format.patch @@ -0,0 +1,86 @@ +diff --git a/fs/extfs/mkfs.cpp b/fs/extfs/mkfs.cpp +index 0195ae5..a9b88b0 100644 +--- a/fs/extfs/mkfs.cpp ++++ b/fs/extfs/mkfs.cpp +@@ -60,11 +60,19 @@ int do_mkfs(io_manager manager, size_t size, char *uuid) { + memset(&fs_param, 0, sizeof(struct ext2_super_block)); + fs_param.s_rev_level = 1; + ++ // Runloop: this image is grown to the target disk size by an offline ++ // resize2fs before it is ever mounted, and it lives on a layered (COW) ++ // block format where every block the grow writes becomes a new block. The ++ // feature set below is chosen to keep that grow as close to free as ++ // possible. + ext2fs_set_feature_64bit(&fs_param); + ext2fs_set_feature_sparse_super(&fs_param); +- ext2fs_set_feature_sparse_super2(&fs_param); + ext2fs_set_feature_filetype(&fs_param); +- ext2fs_set_feature_resize_inode(&fs_param); ++ // Runloop: meta_bg rather than resize_inode. resize_inode reserves a ++ // contiguous GDT area that resize2fs rewrites in full on every grow — ++ // megabytes of COW churn regardless of how far the filesystem grows. ++ // meta_bg reserves nothing and extends the descriptor table in place. ++ ext2fs_set_feature_meta_bg(&fs_param); + ext2fs_set_feature_dir_index(&fs_param); + ext2fs_set_feature_xattr(&fs_param); + ext2fs_set_feature_dir_nlink(&fs_param); +@@ -73,10 +81,23 @@ int do_mkfs(io_manager manager, size_t size, char *uuid) { + ext2fs_set_feature_flex_bg(&fs_param); + ext2fs_set_feature_extents(&fs_param); + ext2fs_set_feature_extra_isize(&fs_param); ++ // Runloop: metadata_csum marks the block groups a grow appends as ++ // uninitialized, so their inode tables are never zeroed — the grow writes ++ // descriptors and nothing else — and checksums every metadata structure, ++ // which is the only damage signal available on a filesystem carrying no ++ // journal. It seeds those checksums from s_uuid, so the seed must be ++ // re-derived once the real UUID is installed below. ++ ext2fs_set_feature_metadata_csum(&fs_param); ++ // Runloop: sparse_super2 must stay off. It places its second backup ++ // superblock in the last block group; growing the filesystem relocates ++ // that backup and frees one block too many, handing out the group's own ++ // block bitmap as free space. + + fs_param.s_log_cluster_size = fs_param.s_log_block_size = 2; + fs_param.s_desc_size = EXT2_MIN_DESC_SIZE_64BIT; +- fs_param.s_log_groups_per_flex = 0; ++ // Runloop: 16 groups per flex group, keeping bitmaps and inode tables ++ // clustered rather than one copy per group at a fixed offset inside it. ++ fs_param.s_log_groups_per_flex = 4; + fs_param.s_inode_size = inode_size; + + ext2fs_blocks_count_set(&fs_param, blocks_count); +@@ -84,8 +105,6 @@ int do_mkfs(io_manager manager, size_t size, char *uuid) { + fs_param.s_inodes_count = (n > UINT_MAX) ? UINT_MAX : n; + + ext2fs_r_blocks_count_set(&fs_param, reserved_ratio * ext2fs_blocks_count(&fs_param)); +- fs_param.s_backup_bgs[0] = 1; +- fs_param.s_backup_bgs[1] = ~0; + + ext2_filsys fs; + // init superblock +@@ -96,6 +115,10 @@ int do_mkfs(io_manager manager, size_t size, char *uuid) { + + uuid4_parse(uuid, (char*)(fs->super->s_uuid)); + uuid4_parse(uuid, (char*)(fs_param.s_hash_seed)); ++ // Runloop: metadata checksums are seeded from s_uuid, which ext2fs_initialize ++ // saw as zeroes. Re-derive the seed against the UUID just installed, before ++ // anything below writes a checksummed structure. ++ ext2fs_init_csum_seed(fs); + fs->super->s_kbytes_written = 1; + fs->super->s_def_hash_version = EXT2_HASH_HALF_MD4; + fs->super->s_max_mnt_count = -1; +@@ -121,9 +144,11 @@ int do_mkfs(io_manager manager, size_t size, char *uuid) { + ext2fs_inode_alloc_stats2(fs, i, +1, 0); + ext2fs_mark_ib_dirty(fs); + // create resize inode +- ret = ext2fs_create_resize_inode(fs); +- if (ret) { +- LOG_ERRNO_RETURN(0, -1, "error creating resize inode ", VALUE(ret)); ++ if (ext2fs_has_feature_resize_inode(fs->super)) { ++ ret = ext2fs_create_resize_inode(fs); ++ if (ret) { ++ LOG_ERRNO_RETURN(0, -1, "error creating resize inode ", VALUE(ret)); ++ } + } + + ret = ext2fs_close_free(&fs); diff --git a/CMake/patches/photon-v0.6.17-ext4-uninit-bg.patch b/CMake/patches/photon-v0.6.17-ext4-uninit-bg.patch deleted file mode 100644 index 007ab09d..00000000 --- a/CMake/patches/photon-v0.6.17-ext4-uninit-bg.patch +++ /dev/null @@ -1,16 +0,0 @@ ---- a/fs/extfs/mkfs.cpp 2026-07-22 10:34:58 -+++ b/fs/extfs/mkfs.cpp 2026-07-22 10:34:58 -@@ -73,6 +73,13 @@ - ext2fs_set_feature_flex_bg(&fs_param); - ext2fs_set_feature_extents(&fs_param); - ext2fs_set_feature_extra_isize(&fs_param); -+ // Runloop: enable uninitialized block groups (gdt_csum). An offline -+ // resize2fs grow then marks new inode tables uninitialized instead of -+ // zeroing them — a near-instant grow with minimal writes into the -+ // layered (COW) block format. (metadata_csum is avoided: it seeds -+ // checksums from s_uuid, which is set after ext2fs_initialize below, so -+ // it would produce an invalid superblock checksum.) -+ ext2fs_set_feature_gdt_csum(&fs_param); - - fs_param.s_log_cluster_size = fs_param.s_log_block_size = 2; - fs_param.s_desc_size = EXT2_MIN_DESC_SIZE_64BIT; From 76ac035fa1750062ef09257c1354a5cd28fa260d Mon Sep 17 00:00:00 2001 From: Reflex Date: Tue, 11 Aug 2026 20:36:08 +0000 Subject: [PATCH 2/2] fix(mkfs): set the checksum algorithm metadata_csum requires ext2fs_initialize leaves s_checksum_type at zero, which ext2fs_open rejects as an unknown checksum, so the image failed to reopen after mkfs. mke2fs sets the field explicitly once the feature is on; do the same. Co-Authored-By: Claude Opus 5 --- .../photon-v0.6.17-ext4-rootfs-format.patch | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/CMake/patches/photon-v0.6.17-ext4-rootfs-format.patch b/CMake/patches/photon-v0.6.17-ext4-rootfs-format.patch index ae0991e7..e7b950f9 100644 --- a/CMake/patches/photon-v0.6.17-ext4-rootfs-format.patch +++ b/CMake/patches/photon-v0.6.17-ext4-rootfs-format.patch @@ -1,5 +1,5 @@ diff --git a/fs/extfs/mkfs.cpp b/fs/extfs/mkfs.cpp -index 0195ae5..a9b88b0 100644 +index 0195ae5..60a0aa7 100644 --- a/fs/extfs/mkfs.cpp +++ b/fs/extfs/mkfs.cpp @@ -60,11 +60,19 @@ int do_mkfs(io_manager manager, size_t size, char *uuid) { @@ -58,18 +58,21 @@ index 0195ae5..a9b88b0 100644 ext2_filsys fs; // init superblock -@@ -96,6 +115,10 @@ int do_mkfs(io_manager manager, size_t size, char *uuid) { +@@ -96,6 +115,13 @@ int do_mkfs(io_manager manager, size_t size, char *uuid) { uuid4_parse(uuid, (char*)(fs->super->s_uuid)); uuid4_parse(uuid, (char*)(fs_param.s_hash_seed)); -+ // Runloop: metadata checksums are seeded from s_uuid, which ext2fs_initialize -+ // saw as zeroes. Re-derive the seed against the UUID just installed, before -+ // anything below writes a checksummed structure. ++ // Runloop: finish what metadata_csum needs and ext2fs_initialize does not ++ // do, before anything below writes a checksummed structure. The algorithm ++ // is not implied by the feature bit — a superblock left at type 0 is ++ // rejected as an unknown checksum on open — and the seed is derived from ++ // s_uuid, which ext2fs_initialize saw as zeroes. ++ fs->super->s_checksum_type = EXT2_CRC32C_CHKSUM; + ext2fs_init_csum_seed(fs); fs->super->s_kbytes_written = 1; fs->super->s_def_hash_version = EXT2_HASH_HALF_MD4; fs->super->s_max_mnt_count = -1; -@@ -121,9 +144,11 @@ int do_mkfs(io_manager manager, size_t size, char *uuid) { +@@ -121,9 +147,11 @@ int do_mkfs(io_manager manager, size_t size, char *uuid) { ext2fs_inode_alloc_stats2(fs, i, +1, 0); ext2fs_mark_ib_dirty(fs); // create resize inode