]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysinstall: don't ask whether to erase a disk that contains no partitions
authorZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Thu, 16 Jul 2026 16:11:31 +0000 (18:11 +0200)
committerZbigniew Jędrzejewski-Szmek <zbyszek@amutable.com>
Fri, 17 Jul 2026 07:18:33 +0000 (09:18 +0200)
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
src/shared/varlink-io.systemd.Repart.c
src/sysinstall/sysinstall.c

index 7a6c542c692a4265dad020341eaf3c0752d489f1..31277c8208253ffd6304c298a08bf3c3c0d87db9 100644 (file)
@@ -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);
index 968d643f017c040823e190eba9aece635065c2c6..33b16525ae26f8c09a6c43672ebadf2bcfd877e9 100644 (file)
@@ -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."),
index 7a531edfcc3b93b06f1fd27bf20db90174030746..b3b730b914a346b256b4dc4155e8652bfce4d9ce 100644 (file)
@@ -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,
                                 &current_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,
                         &current_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;