From 588275f426d64be68db4fbc5471e3e1c2368fb1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Jul 2026 13:33:55 +0200 Subject: [PATCH 1/6] repart: report the actual block device size in currentSizeBytes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The io.systemd.Repart interface documents the currentSizeBytes field as the size of the selected block device, both in the Run() method's dry-run reply and in the InsufficientFreeSpace and DiskTooSmall errors. The implementation however filled it in from the "current size" computed by determine_auto_size(), which means something else entirely: the size of the image as it currently exists, i.e. the GPT metadata overhead plus the sizes of all existing partitions — a metric designed for growing image files with --size=auto. For a disk that is being partitioned from scratch (i.e. carries no partition table yet) that value is 0. As a result, when systemd-sysinstall was pointed at a blank 2G disk that is too small for the OS installation, it reported: The selected disk is not large enough for an OS installation. The size of the selected disk is 0B, but a minimal size of 15.7G is required. Report context->total instead, i.e. the actual size of the block device, which is also the value the DiskTooSmall check compares the required size against. Do this in all three places that send currentSizeBytes, matching the documented semantics of the field. --- src/repart/repart.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 7eadfe4a6a..fdca12a847 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -11582,7 +11582,7 @@ static int vl_method_run( return sd_varlink_errorbo( link, "io.systemd.Repart.DiskTooSmall", - SD_JSON_BUILD_PAIR_UNSIGNED("currentSizeBytes", current_size), + SD_JSON_BUILD_PAIR_UNSIGNED("currentSizeBytes", context->total), SD_JSON_BUILD_PAIR_UNSIGNED("minimalSizeBytes", minimal_size)); /* Or if the disk would fit, but theres's not enough unallocated space */ @@ -11590,7 +11590,7 @@ static int vl_method_run( return sd_varlink_errorbo( link, "io.systemd.Repart.InsufficientFreeSpace", - SD_JSON_BUILD_PAIR_UNSIGNED("currentSizeBytes", current_size), + SD_JSON_BUILD_PAIR_UNSIGNED("currentSizeBytes", context->total), JSON_BUILD_PAIR_UNSIGNED_NON_ZERO("needFreeBytes", need_free), SD_JSON_BUILD_PAIR_UNSIGNED("minimalSizeBytes", minimal_size)); } @@ -11598,17 +11598,17 @@ static int vl_method_run( return r; if (p.dry_run) { - uint64_t current_size, minimal_size; + uint64_t minimal_size; /* If we are doing a dry-run, report the minimal size. */ - r = determine_auto_size(context, LOG_DEBUG, ¤t_size, /* ret_foreign_size= */ NULL, &minimal_size); + r = determine_auto_size(context, LOG_DEBUG, /* ret_current_size= */ NULL, /* ret_foreign_size= */ NULL, &minimal_size); if (r < 0) return r; return sd_varlink_replybo( link, SD_JSON_BUILD_PAIR_UNSIGNED("minimalSizeBytes", minimal_size), - SD_JSON_BUILD_PAIR_UNSIGNED("currentSizeBytes", current_size)); + SD_JSON_BUILD_PAIR_UNSIGNED("currentSizeBytes", context->total)); } r = context_write_partition_table(context); From 70cc3326407a90c621f7472098f0dbee32b9629c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Jul 2026 13:45:23 +0200 Subject: [PATCH 2/6] repart: log allocation failure at debug level in Varlink service mode When systemd-sysinstall probes whether an installation would fit by calling io.systemd.Repart.Run() in dry-run mode, systemd-repart runs as a child process sharing sysinstall's stderr. When the requested partitions didn't fit, context_ponder() logged its failure at LOG_ERR before vl_method_run() converted it into a structured Varlink error (InsufficientFreeSpace or DiskTooSmall), which the client then reports to the user in its own words. The internal message hence appeared interleaved with the user-facing report: Can't fit requested partitions into available free space (1.9G), refusing. The selected disk is not large enough for an OS installation. The size of the selected disk is 0B, but a minimal size of 15.7G is required. Log at LOG_DEBUG when running as a Varlink service, and keep LOG_ERR for CLI invocations, where this message is the primary error report shown to the user. --- src/repart/repart.c | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index fdca12a847..7a6c542c69 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -11402,10 +11402,13 @@ static int context_ponder(Context *context) { if (context_drop_or_foreignize_one_priority(context)) continue; /* Still no luck. Let's drop a priority and try again. */ - /* No more priorities left to drop. This configuration just doesn't fit on this disk... */ - return log_error_errno(SYNTHETIC_ERRNO(ENOSPC), - "Can't fit requested partitions into available free space (%s), refusing.", - FORMAT_BYTES(largest_free_area)); + /* No more priorities left to drop. This configuration just doesn't fit on this disk... + * In Varlink service mode the failure is reported to the client as a structured error, + * hence only log at debug level here. */ + return log_full_errno(arg_varlink ? LOG_DEBUG : LOG_ERR, + SYNTHETIC_ERRNO(ENOSPC), + "Can't fit requested partitions into available free space (%s), refusing.", + FORMAT_BYTES(largest_free_area)); } LIST_FOREACH(partitions, p, context->partitions) { From efbc4481cbbbea448cfe90b7354cf323576396cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Jul 2026 19:03:18 +0200 Subject: [PATCH 3/6] sysinstall: suppress 'no' prompt to begin installation The user has to type 'yes', but it doesn't mean that the default of 'no' is ever useful. Suppress it, so the user doesn't have to press backspace twice. --- src/cryptenroll/cryptenroll-interactive.c | 2 +- src/shared/prompt-util.c | 8 ++++---- src/shared/prompt-util.h | 2 +- src/sysinstall/sysinstall.c | 1 + 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/cryptenroll/cryptenroll-interactive.c b/src/cryptenroll/cryptenroll-interactive.c index c4b8c998bf..f0c1e83789 100644 --- a/src/cryptenroll/cryptenroll-interactive.c +++ b/src/cryptenroll/cryptenroll-interactive.c @@ -151,7 +151,7 @@ static int ask_wipe(EnrollContext *c, unsigned existing_mask) { return log_oom(); bool wipe; - r = prompt_loop_yes_no(question, /* def= */ false, &wipe); + r = prompt_loop_yes_no(question, "no", /* def= */ false, &wipe); if (r < 0) return r; diff --git a/src/shared/prompt-util.c b/src/shared/prompt-util.c index a7bcfe32e3..0a5ab0291b 100644 --- a/src/shared/prompt-util.c +++ b/src/shared/prompt-util.c @@ -217,19 +217,19 @@ static int boolean_is_valid(const char *name, void *userdata) { return parse_boolean(name) >= 0; } -int prompt_loop_yes_no(const char *question, bool def, bool *ret) { +int prompt_loop_yes_no(const char *question, const char *prefill, bool def, bool *ret) { int r; assert(question); assert(ret); - char **l = STRV_MAKE("yes", "no"); + char **menu = STRV_MAKE("yes", "no"); _cleanup_free_ char *reply = NULL; r = prompt_loop(question, GLYPH_WARNING_SIGN, - /* prefill= */ def ? "yes" : "no", - /* menu= */ l, + /* prefill= */ prefill, + /* menu= */ menu, /* accepted= */ NULL, /* ellipsize_percentage= */ 20, /* n_columns= */ 2, diff --git a/src/shared/prompt-util.h b/src/shared/prompt-util.h index 19ab497f08..7c35cb5e58 100644 --- a/src/shared/prompt-util.h +++ b/src/shared/prompt-util.h @@ -26,7 +26,7 @@ int prompt_loop(const char *text, PromptFlags flags, char **ret); -int prompt_loop_yes_no(const char *question, bool def, bool *ret); +int prompt_loop_yes_no(const char *question, const char *prefill, bool def, bool *ret); int chrome_show(const char *top, const char *bottom); void chrome_hide(void); diff --git a/src/sysinstall/sysinstall.c b/src/sysinstall/sysinstall.c index 82232f8ccb..7a531edfcc 100644 --- a/src/sysinstall/sysinstall.c +++ b/src/sysinstall/sysinstall.c @@ -903,6 +903,7 @@ static int prompt_confirm(void) { bool yes; r = prompt_loop_yes_no(arg_summary ? "Please type 'yes' to confirm the choices above and begin the installation" : "Please type 'yes' to begin the installation", + /* prefill= */ NULL, /* def= */ false, &yes); if (r < 0) From 9d8f8d55ed15ec13601cfbb455d61dd4b344df5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Jul 2026 18:11:31 +0200 Subject: [PATCH 4/6] sysinstall: don't ask whether to erase a disk that contains no partitions When the target disk carried no partitions, the installer still asked: Please type 'keep' to install the OS in addition to what the disk already contains, or 'erase' to erase all data on the disk: The question is meaningless in that case: there's nothing on the disk worth preserving, and both answers lead to the same result. Modify fsystemd-repart to report in the dry-run reply of io.systemd.Repart.Run() the number of partitions currently on the disk. The field is only included if the existing partition table was actually read, i.e. in the 'refuse' and 'allow' empty modes, and omitted otherwise. Make systemd-sysinstall skip the erase question if the reply positively indicates that there are no partitions, proceeding as if 'keep' was selected. --- src/repart/repart.c | 13 ++++++- src/shared/varlink-io.systemd.Repart.c | 2 ++ src/sysinstall/sysinstall.c | 50 +++++++++++++++++--------- 3 files changed, 48 insertions(+), 17 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index 7a6c542c69..31277c8208 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -11608,10 +11608,21 @@ static int vl_method_run( if (r < 0) return r; + bool count_partitions = IN_SET(context->empty, EMPTY_REFUSE, EMPTY_ALLOW); + uint64_t n_partitions = 0; + + if (count_partitions) + LIST_FOREACH(partitions, pp, context->partitions) + n_partitions += PARTITION_EXISTS(pp); + + /* In 'force' and 'require' modes the existing partition table is not read, hence we don't + * know how many partitions the disk contains, and say nothing about it. */ return sd_varlink_replybo( link, SD_JSON_BUILD_PAIR_UNSIGNED("minimalSizeBytes", minimal_size), - SD_JSON_BUILD_PAIR_UNSIGNED("currentSizeBytes", context->total)); + SD_JSON_BUILD_PAIR_UNSIGNED("currentSizeBytes", context->total), + SD_JSON_BUILD_PAIR_CONDITION(count_partitions, + "partitionCount", SD_JSON_BUILD_UNSIGNED(n_partitions))); } r = context_write_partition_table(context); diff --git a/src/shared/varlink-io.systemd.Repart.c b/src/shared/varlink-io.systemd.Repart.c index 968d643f01..33b16525ae 100644 --- a/src/shared/varlink-io.systemd.Repart.c +++ b/src/shared/varlink-io.systemd.Repart.c @@ -59,6 +59,8 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_DEFINE_OUTPUT(minimalSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("In dry-run mode returns the size of the selected block device."), SD_VARLINK_DEFINE_OUTPUT(currentSizeBytes, SD_VARLINK_INT, SD_VARLINK_NULLABLE), + SD_VARLINK_FIELD_COMMENT("Returns the number of partitions currently on the selected block device. Omitted if not determined by the workflow, e.g. if the existing partition table was not read because empty mode 'force' or 'require' was selected."), + SD_VARLINK_DEFINE_OUTPUT(partitionCount, SD_VARLINK_INT, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a phase identifier is sent in progress updates."), SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(phase, ProgressPhase, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, an object identifier string is sent in progress updates."), diff --git a/src/sysinstall/sysinstall.c b/src/sysinstall/sysinstall.c index 7a531edfcc..b3b730b914 100644 --- a/src/sysinstall/sysinstall.c +++ b/src/sysinstall/sysinstall.c @@ -689,11 +689,12 @@ static int sysinstall_context_invoke_repart_run(SysInstallContext *context) { return result.ret; } -static int read_space_metrics( +static int read_repart_metrics( sd_json_variant *v, uint64_t *min_size, uint64_t *current_size, - uint64_t *need_free) { + uint64_t *need_free, + uint64_t *n_partitions) { int r; @@ -701,16 +702,19 @@ static int read_space_metrics( uint64_t min_size; uint64_t current_size; uint64_t need_free; + uint64_t n_partitions; } p = { .min_size = UINT64_MAX, .current_size = UINT64_MAX, .need_free = UINT64_MAX, + .n_partitions = UINT64_MAX, }; static const sd_json_dispatch_field dispatch_table[] = { { "minimalSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, min_size), 0 }, { "currentSizeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, current_size), 0 }, { "needFreeBytes", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, need_free), 0 }, + { "partitionCount", _SD_JSON_VARIANT_TYPE_INVALID, sd_json_dispatch_uint64, voffsetof(p, n_partitions), 0 }, {} }; @@ -724,6 +728,8 @@ static int read_space_metrics( *current_size = p.current_size; if (need_free) *need_free = p.need_free; + if (n_partitions) + *n_partitions = p.n_partitions; return 0; } @@ -736,7 +742,8 @@ static int invoke_repart( char **definitions, uint64_t *min_size, /* initialized both on success and error */ uint64_t *current_size, /* ditto */ - uint64_t *need_free) { /* ditto */ + uint64_t *need_free, /* ditto */ + uint64_t *n_partitions) { /* ditto; UINT64_MAX if not determined */ int r; @@ -747,7 +754,7 @@ static int invoke_repart( r = connect_to_repart(link); if (r < 0) { - read_space_metrics(/* v= */ NULL, min_size, current_size, need_free); + read_repart_metrics(/* v= */ NULL, min_size, current_size, need_free, n_partitions); return r; } @@ -772,19 +779,19 @@ static int invoke_repart( SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsEmpty", true), SD_JSON_BUILD_PAIR_BOOLEAN("deferPartitionsFactoryReset", true)); if (r < 0) { - read_space_metrics(/* v= */ NULL, min_size, current_size, need_free); + read_repart_metrics(/* v= */ NULL, min_size, current_size, need_free, n_partitions); return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %m"); } if (error_id) { if (streq(error_id, "io.systemd.Repart.InsufficientFreeSpace")) { - (void) read_space_metrics(reply, min_size, current_size, need_free); + (void) read_repart_metrics(reply, min_size, current_size, need_free, n_partitions); return log_full_errno( dry_run ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(ENOSPC), "Not enough free space on disk, cannot install."); } if (streq(error_id, "io.systemd.Repart.DiskTooSmall")) { - (void) read_space_metrics(reply, min_size, current_size, need_free); + (void) read_repart_metrics(reply, min_size, current_size, need_free, n_partitions); return log_full_errno( dry_run ? LOG_DEBUG : LOG_ERR, SYNTHETIC_ERRNO(E2BIG), @@ -792,7 +799,7 @@ static int invoke_repart( } /* For all other errors reset the metrics */ - read_space_metrics(/* v= */ NULL, min_size, current_size, need_free); + read_repart_metrics(/* v= */ NULL, min_size, current_size, need_free, n_partitions); if (streq(error_id, "io.systemd.Repart.ConflictingDiskLabelPresent")) return log_full_errno( @@ -807,7 +814,7 @@ static int invoke_repart( return log_error_errno(r, "Failed to issue io.systemd.Repart.Run() varlink call: %s", error_id); } - (void) read_space_metrics(reply, min_size, current_size, need_free); + (void) read_repart_metrics(reply, min_size, current_size, need_free, n_partitions); return 0; } @@ -923,7 +930,8 @@ static int validate_run(sd_varlink **repart_link, const char *node) { /* First loop: either with explicitly configured --erase= value, or false. A second loop only if not configured explicitly. */ bool try_erase = arg_erase > 0, conflicting_disk_label = false; for (;;) { - uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX; + uint64_t min_size = UINT64_MAX, current_size = UINT64_MAX, need_free = UINT64_MAX, + n_partitions = UINT64_MAX; r = invoke_repart( repart_link, node, @@ -932,7 +940,8 @@ static int validate_run(sd_varlink **repart_link, const char *node) { arg_definitions, &min_size, ¤t_size, - &need_free); + &need_free, + &n_partitions); if (r == -ENOSPC) { /* The disk is large enough, but there's not enough unallocated space. Hence proceed, but require erasing */ if (try_erase || arg_erase >= 0) @@ -970,9 +979,16 @@ static int validate_run(sd_varlink **repart_link, const char *node) { log_warning("A conflicting disk label has been found, and must be erased for installation."); if (arg_erase < 0) { - r = prompt_erase(/* can_add= */ !try_erase, &arg_erase); - if (r < 0) - return r; + if (n_partitions == 0) { + /* If the disk contains no partitions there's nothing worth preserving on + * it, hence don't bother asking whether to erase it. */ + log_info("The selected disk contains no partitions, proceeding."); + arg_erase = false; + } else { + r = prompt_erase(/* can_add= */ !try_erase, &arg_erase); + if (r < 0) + return r; + } } return 0; @@ -1754,7 +1770,8 @@ static int fetch_candidate_devices_reply( context->definitions, &min_size, ¤t_size, - &need_free); + &need_free, + /* n_partitions= */ NULL); DeviceFit fit; if (r < 0) { @@ -2132,7 +2149,8 @@ static int run(int argc, char *argv[]) { arg_definitions, &min_size, /* current_size= */ NULL, - /* need_free= */ NULL); + /* need_free= */ NULL, + /* n_partitions= */ NULL); if (r < 0) return r; From d094067547fb8dd91c637981cf6b1870ee2c08de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Thu, 16 Jul 2026 15:17:33 +0200 Subject: [PATCH 5/6] network: silence false warning about unitialized variable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [1177/3647] Compiling C object systemd-networkd.p/src_network_networkd-bridge-vlan.c.o In function ‘bridge_vlan_append_set_info’, inlined from ‘bridge_vlan_set_message’ at ../src/network/networkd-bridge-vlan.c:257:21: ../src/network/networkd-bridge-vlan.c:162:28: warning: ‘untagged’ may be used uninitialized [-Wmaybe-uninitialized] 162 | if (untagged == u) | ^ ../src/network/networkd-bridge-vlan.c: In function ‘bridge_vlan_set_message’: ../src/network/networkd-bridge-vlan.c:111:14: note: ‘untagged’ was declared here 111 | bool untagged, pvid_is_untagged; | ^~~~~~~~ --- src/network/networkd-bridge-vlan.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/network/networkd-bridge-vlan.c b/src/network/networkd-bridge-vlan.c index 9701811876..ed1aa5e1df 100644 --- a/src/network/networkd-bridge-vlan.c +++ b/src/network/networkd-bridge-vlan.c @@ -108,7 +108,8 @@ static uint16_t link_get_pvid(Link *link, bool *ret_untagged) { static int bridge_vlan_append_set_info(Link *link, sd_netlink_message *m) { _cleanup_free_ char *str = NULL; uint16_t pvid, begin = UINT16_MAX; - bool untagged, pvid_is_untagged; + bool pvid_is_untagged, untagged = false; /* is always set togather with . + * Initialize to appease gcc. */ int r; assert(link); From a48ff4de4664b74338126cc61d9cca5b435358b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Fri, 17 Jul 2026 09:21:59 +0200 Subject: [PATCH 6/6] repart: fix varlink description string Fixup for 1c76e204d3eb2e43918ddd18c9a93eed8facedfd. --- src/shared/varlink-io.systemd.Repart.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/shared/varlink-io.systemd.Repart.c b/src/shared/varlink-io.systemd.Repart.c index 33b16525ae..7c6f799148 100644 --- a/src/shared/varlink-io.systemd.Repart.c +++ b/src/shared/varlink-io.systemd.Repart.c @@ -65,7 +65,7 @@ static SD_VARLINK_DEFINE_METHOD_FULL( SD_VARLINK_DEFINE_OUTPUT_BY_TYPE(phase, ProgressPhase, SD_VARLINK_NULLABLE), SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, an object identifier string is sent in progress updates."), SD_VARLINK_DEFINE_OUTPUT(object, SD_VARLINK_STRING, SD_VARLINK_NULLABLE), - SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a progress percentrage (specific to the work done for the specified phase+object is sent in progress updates."), + SD_VARLINK_FIELD_COMMENT("If used with the 'more' flag, a progress percentage (specific to the work done for the specified phase+object)."), SD_VARLINK_DEFINE_OUTPUT(progress, SD_VARLINK_INT, SD_VARLINK_NULLABLE)); static SD_VARLINK_DEFINE_METHOD_FULL(