From: Nandakumar Raghavan Date: Wed, 25 Feb 2026 06:38:31 +0000 (+0000) Subject: repart: add --grain-size= option for partition alignment X-Git-Tag: v261-rc1~821 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=36d129a7adae13a95e5ccbe10a5142c268c0882f;p=thirdparty%2Fsystemd.git repart: add --grain-size= option for partition alignment Add a --grain-size= CLI option to override the default 4 KiB partition alignment grain. Setting --grain-size=1M matches the alignment used by fdisk/parted and fixes misaligned partitions after small fixed-size partitions like the 16 KiB verity-sig partition. Also fix context_place_partitions() to re-align the start offset after each partition, not just once per free area. Without this, a small partition would cause all subsequent partitions in the same free area to start at an unaligned offset. --- diff --git a/man/systemd-repart.xml b/man/systemd-repart.xml index e13dac3cd53..dac47595384 100644 --- a/man/systemd-repart.xml +++ b/man/systemd-repart.xml @@ -535,6 +535,19 @@ + + + + This option controls the partition alignment granularity used when placing + partitions. It takes a power-of-2 value that is at least the sector size. All partition start + offsets will be rounded up to a multiple of this value. Defaults to + MAX(4096, sector_size), matching the conventional 4 KiB alignment. Setting + this to 1M ensures proper alignment for modern storage devices even after + small fixed-size partitions such as a verity signature partition. + + + + diff --git a/src/repart/repart.c b/src/repart/repart.c index f6ca9aca056..eb334a7c401 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -200,6 +200,7 @@ static size_t arg_n_defer_partitions = 0; static bool arg_defer_partitions_empty = false; static bool arg_defer_partitions_factory_reset = false; static uint64_t arg_sector_size = 0; +static uint64_t arg_grain_size = 0; static ImagePolicy *arg_image_policy = NULL; static Architecture arg_architecture = _ARCHITECTURE_INVALID; static int arg_offline = -1; @@ -617,6 +618,10 @@ static const char *progress_phase_table[_PROGRESS_PHASE_MAX] = { [PROGRESS_REREADING_TABLE] = "rereading-table", }; +static uint64_t determine_grain_size(uint64_t sector_size) { + return MAX(arg_grain_size > 0 ? arg_grain_size : 4096U, sector_size); +} + DEFINE_PRIVATE_STRING_TABLE_LOOKUP(empty_mode, EmptyMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP(append_mode, AppendMode); DEFINE_PRIVATE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_BOOLEAN(encrypt_mode, EncryptMode, ENCRYPT_KEY_FILE); @@ -1701,7 +1706,7 @@ static void context_place_partitions(Context *context) { for (size_t i = 0; i < context->n_free_areas; i++) { FreeArea *a = context->free_areas[i]; - _unused_ uint64_t left; + uint64_t left; uint64_t start; if (a->after) { @@ -1717,6 +1722,8 @@ static void context_place_partitions(Context *context) { left = a->size; LIST_FOREACH(partitions, p, context->partitions) { + uint64_t gap; + if (p->allocated_to_area != a) continue; @@ -1730,6 +1737,21 @@ static void context_place_partitions(Context *context) { assert(left >= p->new_padding); start += p->new_padding; left -= p->new_padding; + + /* Re-align start to the grain after each partition, so that the next + * partition placed into this free area also starts on a grain boundary. + * This matters when the grain is larger than the default (e.g. 1 MiB via + * --grain-size=) and a small partition like verity-sig (16 KiB) precedes + * a larger one: without this, the successor would start at an unaligned + * offset. */ + gap = round_up_size(start, context->grain_size) - start; + if (gap > left) { + log_warning("Not enough space left in free area to re-align partition start to grain size, " + "next partition may start at an unaligned offset."); + gap = 0; + } + start += gap; + left -= gap; } } } @@ -3579,7 +3601,7 @@ static int context_load_fallback_metrics(Context *context) { assert(context); context->sector_size = arg_sector_size > 0 ? arg_sector_size : 512; - context->grain_size = MAX(context->sector_size, 4096U); + context->grain_size = determine_grain_size(context->sector_size); context->default_fs_sector_size = arg_sector_size > 0 ? arg_sector_size : DEFAULT_FILESYSTEM_SECTOR_SIZE; return 1; /* Starting from scratch */ } @@ -3672,7 +3694,7 @@ static int context_load_partition_table(Context *context) { /* Use the fallback values if we have no better idea */ context->sector_size = fdisk_get_sector_size(c); context->default_fs_sector_size = fs_secsz; - context->grain_size = MAX(context->sector_size, 4096U); + context->grain_size = determine_grain_size(context->sector_size); return /* from_scratch= */ true; } @@ -3701,9 +3723,9 @@ static int context_load_partition_table(Context *context) { if (secsz < 512 || !ISPOWEROF2(secsz)) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size %lu is not a power of two larger than 512? Refusing.", secsz); - /* Use at least 4K, and ensure it's a multiple of the sector size, regardless if that is smaller or - * larger */ - grainsz = MAX(secsz, 4096U); + /* Determine the grain size: by default at least 4K and a multiple of the sector size, but may be + * overridden via --grain-size=. */ + grainsz = determine_grain_size(secsz); log_debug("Sector size of device is %lu bytes. Using default filesystem sector size of %" PRIu64 " and grain size of %" PRIu64 ".", secsz, fs_secsz, grainsz); @@ -9069,6 +9091,7 @@ static int help(void) { " --offline=BOOL Whether to build the image offline\n" " --discard=BOOL Whether to discard backing blocks for new partitions\n" " --sector-size=SIZE Set the logical sector size for the image\n" + " --grain-size=BYTES Set the grain size for partition alignment\n" " --architecture=ARCH Set the generic architecture for the image\n" " --size=BYTES Grow loopback file to specified size\n" " --seed=UUID 128-bit seed UUID to derive all UUIDs from\n" @@ -9199,6 +9222,7 @@ static int parse_argv(int argc, char *argv[]) { ARG_DEFER_PARTITIONS_EMPTY, ARG_DEFER_PARTITIONS_FACTORY_RESET, ARG_SECTOR_SIZE, + ARG_GRAIN_SIZE, ARG_SKIP_PARTITIONS, ARG_ARCHITECTURE, ARG_OFFLINE, @@ -9248,6 +9272,7 @@ static int parse_argv(int argc, char *argv[]) { { "defer-partitions-empty", required_argument, NULL, ARG_DEFER_PARTITIONS_EMPTY }, { "defer-partitions-factory-reset", required_argument, NULL, ARG_DEFER_PARTITIONS_FACTORY_RESET }, { "sector-size", required_argument, NULL, ARG_SECTOR_SIZE }, + { "grain-size", required_argument, NULL, ARG_GRAIN_SIZE }, { "architecture", required_argument, NULL, ARG_ARCHITECTURE }, { "offline", required_argument, NULL, ARG_OFFLINE }, { "copy-from", required_argument, NULL, ARG_COPY_FROM }, @@ -9570,6 +9595,15 @@ static int parse_argv(int argc, char *argv[]) { break; + case ARG_GRAIN_SIZE: + r = parse_size(optarg, 1024, &arg_grain_size); + if (r < 0) + return log_error_errno(r, "Failed to parse --grain-size= parameter: %s", optarg); + if (arg_grain_size < 512 || !ISPOWEROF2(arg_grain_size)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Grain size must be a power of 2 >= 512."); + + break; + case ARG_ARCHITECTURE: r = architecture_from_string(optarg); if (r < 0)