repart: if device node is specified as "-", calculate needed disk space

So far repart always required specification of a device node. And if
none was specified, then we'd fine the node backing the root fs. Let's
optionally allow that the device node is explicitly not specified (i.e.
specified as "-" or ""), in which case we'll just print the size of the
minimal image given the definitions.
This commit is contained in:
Lennart Poettering
2025-08-29 10:40:24 +02:00
parent 0eab6d2960
commit 2ecfea7491
2 changed files with 83 additions and 42 deletions

View File

@@ -26,7 +26,7 @@
<cmdsynopsis>
<command>systemd-repart</command>
<arg choice="opt" rep="repeat">OPTIONS</arg>
<arg choice="opt" rep="repeat"><replaceable><optional>BLOCKDEVICE</optional></replaceable></arg>
<arg choice="opt">BLOCKDEVICE</arg>
</cmdsynopsis>
<para><filename>systemd-repart.service</filename></para>
@@ -35,10 +35,10 @@
<refsect1>
<title>Description</title>
<para><command>systemd-repart</command> creates partition tables, and adds or grows partitions,
based on the configuration files described in
<citerefentry><refentrytitle>repart.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para>
<para><command>systemd-repart</command> creates partition tables, and adds or grows partitions, based on
the configuration files described in
<citerefentry><refentrytitle>repart.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>. It operates
on the block device or file image specified on the command line.</para>
<para><command>systemd-repart</command> is used when <emphasis>building</emphasis> OS images, and also
when <emphasis>deploying</emphasis> images to automatically adjust them, during boot, to the system they
@@ -53,6 +53,11 @@
<filename>systemd-repart.service</filename> service is generally run at boot in the initrd, in order to
augment the partition table of the OS before its partitions are mounted.</para>
<para>If the block device is specified as <literal>-</literal> (or as an empty string),
<command>systemd-repart</command> will not operate on any block device or image file, and instead
determine and output the minimum disk/image size for the specified partition configuration, taking all
configured size constraints into account.</para>
<para><command>systemd-repart</command> operations are mostly incremental: it grows existing partitions
or adds new ones, but does not shrink, delete, or move existing partitions. The service is intended to be
run on every boot, but when it detects that the partition table already matches the installed

View File

@@ -159,6 +159,7 @@ typedef enum AppendMode {
static EmptyMode arg_empty = EMPTY_UNSET;
static bool arg_dry_run = true;
static char *arg_node = NULL;
static bool arg_node_none = false;
static char *arg_root = NULL;
static char *arg_image = NULL;
static char **arg_definitions = NULL;
@@ -830,6 +831,7 @@ static Context* context_new(
.private_key = private_key,
.empty = empty,
.dry_run = dry_run,
.backing_fd = -EBADF,
};
return context;
@@ -3410,6 +3412,15 @@ static void derive_salt(sd_id128_t base, const char *token, uint8_t ret[static S
hmac_sha256(base.bytes, sizeof(base.bytes), token, strlen(token), ret);
}
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->default_fs_sector_size = arg_sector_size > 0 ? arg_sector_size : DEFAULT_FILESYSTEM_SECTOR_SIZE;
return 1; /* Starting from scratch */
}
static int context_load_partition_table(Context *context) {
_cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
_cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
@@ -3422,6 +3433,7 @@ static int context_load_partition_table(Context *context) {
int r;
assert(context);
assert(context->node);
assert(!context->fdisk_context);
assert(!context->free_areas);
assert(context->start == UINT64_MAX);
@@ -4685,14 +4697,16 @@ static int prepare_temporary_file(Context *context, PartitionTarget *t, uint64_t
if (fd < 0)
return log_error_errno(fd, "Failed to create temporary file: %m");
r = read_attr_fd(fdisk_get_devfd(context->fdisk_context), &attrs);
if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_warning_errno(r, "Failed to read file attributes of %s, ignoring: %m", context->node);
if (context->fdisk_context) {
r = read_attr_fd(fdisk_get_devfd(context->fdisk_context), &attrs);
if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_warning_errno(r, "Failed to read file attributes of %s, ignoring: %m", context->node);
if (FLAGS_SET(attrs, FS_NOCOW_FL)) {
r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
if (r < 0 && !ERRNO_IS_IOCTL_NOT_SUPPORTED(r))
return log_error_errno(r, "Failed to disable copy-on-write on %s: %m", temp);
if (FLAGS_SET(attrs, FS_NOCOW_FL)) {
r = chattr_fd(fd, FS_NOCOW_FL, FS_NOCOW_FL);
if (r < 0 && !ERRNO_IS_IOCTL_NOT_SUPPORTED(r))
return log_error_errno(r, "Failed to disable copy-on-write on %s: %m", temp);
}
}
if (ftruncate(fd, size) < 0)
@@ -8242,9 +8256,11 @@ static int context_minimize(Context *context) {
assert(context);
r = read_attr_fd(context->backing_fd, &attrs);
if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_warning_errno(r, "Failed to read file attributes of %s, ignoring: %m", context->node);
if (context->backing_fd >= 0) {
r = read_attr_fd(context->backing_fd, &attrs);
if (r < 0 && !ERRNO_IS_NEG_NOT_SUPPORTED(r))
log_warning_errno(r, "Failed to read file attributes of %s, ignoring: %m", context->node);
}
LIST_FOREACH(partitions, p, context->partitions) {
_cleanup_(rm_rf_physical_and_freep) char *root = NULL;
@@ -9323,9 +9339,14 @@ static int parse_argv(
}
if (argc > optind) {
arg_node = strdup(argv[optind]);
if (!arg_node)
return log_oom();
if (empty_or_dash(argv[optind]))
arg_node_none = true;
else {
arg_node = strdup(argv[optind]);
if (!arg_node)
return log_oom();
arg_node_none = false;
}
}
if (IN_SET(arg_empty, EMPTY_FORCE, EMPTY_REQUIRE, EMPTY_CREATE) && !arg_node && !arg_image)
@@ -9579,6 +9600,9 @@ static int find_root(Context *context) {
assert(context);
if (arg_node_none)
return 0;
if (arg_node) {
if (context->empty == EMPTY_CREATE) {
_cleanup_close_ int fd = -EBADF;
@@ -9806,7 +9830,7 @@ done:
return 1;
}
static int determine_auto_size(Context *c) {
static int determine_auto_size(Context *c, uint64_t *ret) {
uint64_t sum;
assert(c);
@@ -9828,14 +9852,15 @@ static int determine_auto_size(Context *c) {
if (c->total != UINT64_MAX)
/* Image already allocated? Then show its size. */
log_info("Automatically determined minimal disk image size as %s, current image size is %s.",
log_info("Automatically determined minimal disk image size as %s, current block device/image size is %s.",
FORMAT_BYTES(sum), FORMAT_BYTES(c->total));
else
/* If the image is being created right now, then it has no previous size, suppress any comment about it hence. */
log_info("Automatically determined minimal disk image size as %s.",
FORMAT_BYTES(sum));
arg_size = sum;
if (ret)
*ret = sum;
return 0;
}
@@ -9858,11 +9883,9 @@ static int context_ponder(Context *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... */
r = log_error_errno(SYNTHETIC_ERRNO(ENOSPC),
"Can't fit requested partitions into available free space (%s), refusing.",
FORMAT_BYTES(largest_free_area));
(void) determine_auto_size(context);
return r;
return log_error_errno(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) {
@@ -10050,7 +10073,7 @@ static int run(int argc, char *argv[]) {
if (!arg_root)
return log_oom();
if (!arg_node) {
if (!arg_node && !arg_node_none) {
arg_node = strdup(loop_device->node);
if (!arg_node)
return log_oom();
@@ -10117,21 +10140,24 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
if (arg_size != UINT64_MAX) {
r = resize_backing_fd(
context->node,
&context->backing_fd,
node_is_our_loop ? arg_image : NULL,
node_is_our_loop ? loop_device : NULL,
context->sector_size);
if (r < 0)
return r;
}
if (context->node) {
if (arg_size != UINT64_MAX) {
r = resize_backing_fd(
context->node,
&context->backing_fd,
node_is_our_loop ? arg_image : NULL,
node_is_our_loop ? loop_device : NULL,
context->sector_size);
if (r < 0)
return r;
}
r = context_load_partition_table(context);
if (r == -EHWPOISON)
return 77; /* Special return value which means "Not GPT, so not doing anything". This isn't
* really an error when called at boot. */
r = context_load_partition_table(context);
if (r == -EHWPOISON)
return 77; /* Special return value which means "Not GPT, so not doing anything". This isn't
* really an error when called at boot. */
} else
r = context_load_fallback_metrics(context);
if (r < 0)
return r;
context->from_scratch = r > 0; /* Starting from scratch */
@@ -10194,8 +10220,13 @@ static int run(int argc, char *argv[]) {
if (r < 0)
return r;
if (arg_node_none) {
(void) determine_auto_size(context, /* ret= */ NULL);
return 0;
}
if (arg_size_auto) {
r = determine_auto_size(context);
r = determine_auto_size(context, &arg_size);
if (r < 0)
return r;
@@ -10218,6 +10249,11 @@ static int run(int argc, char *argv[]) {
}
r = context_ponder(context);
if (r == -ENOSPC) {
/* When we hit space issues, tell the user the minimal size. */
(void) determine_auto_size(context, /* ret= */ NULL);
return r;
}
if (r < 0)
return r;