<citerefentry><refentrytitle>repart.d</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
</para>
- <para>If invoked with no arguments, it operates on the block device backing the root file system partition
- of the OS, thus growing and adding partitions of the booted OS image itself. When called in the initial
- RAM disk it operates on the block device backing <filename>/sysroot/</filename> instead, i.e. on the
- block device the system will soon transition into. The <filename>systemd-repart.service</filename>
- service is generally run at boot in the initial RAM disk, in order to augment the partition table of the
- OS before its partitions are mounted. <command>systemd-repart</command> (mostly) operates in a purely
- incremental mode: it only grows existing and adds new partitions; it 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 <filename>repart.d/*.conf</filename> configuration
- files, it executes no operation.</para>
+ <para>If invoked with no arguments, it operates on the block device backing the root file system
+ partition of the running OS, thus growing and adding partitions of the booted OS image itself. If
+ <varname>--image=</varname> is used it will operate on the specified image file. When called in the
+ <literal>initrd</literal> it operates on the block device backing <filename>/sysroot/</filename> instead,
+ i.e. on the block device the system will soon transition into. The
+ <filename>systemd-repart.service</filename> service is generally run at boot in the initial RAM disk, in
+ order to augment the partition table of the OS before its partitions are
+ mounted. <command>systemd-repart</command> (mostly) operates in a purely incremental mode: it only grows
+ existing and adds new partitions; it 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 <filename>repart.d/*.conf</filename> configuration files, it executes no operation.</para>
<para><command>systemd-repart</command> is intended to be used when deploying OS images, to automatically
adjust them to the system they are running on, during first boot. This way the deployed image can be
<term><option>--root=</option></term>
<listitem><para>Takes a path to a directory to use as root file system when searching for
- <filename>repart.d/*.conf</filename> files and for the machine ID file to use as seed. By default
- when invoked on the regular system this defaults to the host's root file system
+ <filename>repart.d/*.conf</filename> files, for the machine ID file to use as seed and for the
+ <varname>CopyFiles=</varname> and <varname>CopyBlocks=</varname> source files and directories. By
+ default when invoked on the regular system this defaults to the host's root file system
<filename>/</filename>. If invoked from the initial RAM disk this defaults to
<filename>/sysroot/</filename>, so that the tool operates on the configuration and machine ID stored
in the root file system later transitioned into itself.</para></listitem>
</varlistentry>
+ <varlistentry>
+ <term><option>--image=</option></term>
+
+ <listitem><para>Takes a path to a disk image file or device to mount and use in a similar fashion to
+ <option>--root=</option>, see above.</para></listitem>
+ </varlistentry>
+
<varlistentry>
<term><option>--seed=</option></term>
static bool arg_dry_run = true;
static const char *arg_node = NULL;
static char *arg_root = NULL;
+static char *arg_image = NULL;
static char *arg_definitions = NULL;
static bool arg_discard = true;
static bool arg_can_factory_reset = false;
static uint32_t arg_tpm2_pcr_mask = UINT32_MAX;
STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
+STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep);
STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep);
STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
" them\n"
" --can-factory-reset Test whether factory reset is defined\n"
" --root=PATH Operate relative to root path\n"
+ " --image=PATH Operate relative to image file\n"
" --definitions=DIR Find partition definitions in specified directory\n"
" --key-file=PATH Key to use when encrypting partitions\n"
" --tpm2-device=PATH Path to TPM2 device node to use\n"
ARG_FACTORY_RESET,
ARG_CAN_FACTORY_RESET,
ARG_ROOT,
+ ARG_IMAGE,
ARG_SEED,
ARG_PRETTY,
ARG_DEFINITIONS,
{ "factory-reset", required_argument, NULL, ARG_FACTORY_RESET },
{ "can-factory-reset", no_argument, NULL, ARG_CAN_FACTORY_RESET },
{ "root", required_argument, NULL, ARG_ROOT },
+ { "image", required_argument, NULL, ARG_IMAGE },
{ "seed", required_argument, NULL, ARG_SEED },
{ "pretty", required_argument, NULL, ARG_PRETTY },
{ "definitions", required_argument, NULL, ARG_DEFINITIONS },
break;
case ARG_ROOT:
- r = parse_path_argument(optarg, false, &arg_root);
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_root);
+ if (r < 0)
+ return r;
+ break;
+
+ case ARG_IMAGE:
+ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_image);
if (r < 0)
return r;
break;
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"If --empty=create is specified, --size= must be specified, too.");
+ if (arg_image && arg_root)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Please specify either --root= or --image=, the combination of both is not supported.");
+ else if (!arg_image && !arg_root && in_initrd()) {
+ /* Default to operation on /sysroot when invoked in the initrd! */
+ arg_root = strdup("/sysroot");
+ if (!arg_root)
+ return log_oom();
+ }
+
arg_node = argc > optind ? argv[optind] : NULL;
- if (IN_SET(arg_empty, EMPTY_FORCE, EMPTY_REQUIRE, EMPTY_CREATE) && !arg_node)
+ if (IN_SET(arg_empty, EMPTY_FORCE, EMPTY_REQUIRE, EMPTY_CREATE) && !arg_node && !arg_image)
return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
"A path to a device node or loopback file must be specified when --empty=force, --empty=require or --empty=create are used.");
return 0;
}
-static int acquire_root_devno(const char *p, int mode, char **ret, int *ret_fd) {
+static int acquire_root_devno(
+ const char *p,
+ const char *root,
+ int mode,
+ char **ret,
+ int *ret_fd) {
+
+ _cleanup_free_ char *found_path = NULL;
+ dev_t devno, fd_devno = MODE_INVALID;
_cleanup_close_ int fd = -1;
struct stat st;
- dev_t devno, fd_devno = MODE_INVALID;
int r;
assert(p);
assert(ret);
assert(ret_fd);
- fd = open(p, mode);
+ fd = chase_symlinks_and_open(p, root, CHASE_PREFIX_ROOT, mode, &found_path);
if (fd < 0)
- return -errno;
+ return fd;
if (fstat(fd, &st) < 0)
return -errno;
if (S_ISREG(st.st_mode)) {
- char *s;
-
- s = strdup(p);
- if (!s)
- return log_oom();
-
- *ret = s;
+ *ret = TAKE_PTR(found_path);
*ret_fd = TAKE_FD(fd);
-
return 0;
}
- if (S_ISBLK(st.st_mode))
+ if (S_ISBLK(st.st_mode)) {
+ /* Refuse referencing explicit block devices if a root dir is specified, after all we should
+ * be able to leave the image the root path constraints us to. */
+ if (root)
+ return -EPERM;
+
fd_devno = devno = st.st_rdev;
- else if (S_ISDIR(st.st_mode)) {
+ } else if (S_ISDIR(st.st_mode)) {
devno = st.st_dev;
if (major(devno) == 0) {
return 0;
}
- r = acquire_root_devno(arg_node, O_RDONLY|O_CLOEXEC, ret, ret_fd);
+ /* Note that we don't specify a root argument here: if the user explicitly configured a node
+ * we'll take it relative to the host, not the image */
+ r = acquire_root_devno(arg_node, NULL, O_RDONLY|O_CLOEXEC, ret, ret_fd);
if (r == -EUCLEAN)
return btrfs_log_dev_root(LOG_ERR, r, arg_node);
if (r < 0)
} else
p = t;
- r = acquire_root_devno(p, O_RDONLY|O_DIRECTORY|O_CLOEXEC, ret, ret_fd);
+ r = acquire_root_devno(p, arg_root, O_RDONLY|O_DIRECTORY|O_CLOEXEC, ret, ret_fd);
if (r < 0) {
if (r == -EUCLEAN)
return btrfs_log_dev_root(LOG_ERR, r, p);
return 1;
}
-static int resize_backing_fd(const char *node, int *fd) {
+static int resize_backing_fd(
+ const char *node, /* The primary way we access the disk image to operate on */
+ int *fd, /* An O_RDONLY fd referring to that inode */
+ const char *backing_file, /* If the above refers to a loopback device, the backing regular file for that, which we can grow */
+ LoopDevice *loop_device) {
+
char buf1[FORMAT_BYTES_MAX], buf2[FORMAT_BYTES_MAX];
_cleanup_close_ int writable_fd = -1;
+ uint64_t current_size;
struct stat st;
int r;
if (fstat(*fd, &st) < 0)
return log_error_errno(errno, "Failed to stat '%s': %m", node);
- r = stat_verify_regular(&st);
- if (r < 0)
- return log_error_errno(r, "Specified path '%s' is not a regular file, cannot resize: %m", node);
+ if (S_ISBLK(st.st_mode)) {
+ if (!backing_file)
+ return log_error_errno(SYNTHETIC_ERRNO(EBADF), "Cannot resize block device '%s'.", node);
+
+ assert(loop_device);
- assert_se(format_bytes(buf1, sizeof(buf1), st.st_size));
+ if (ioctl(*fd, BLKGETSIZE64, ¤t_size) < 0)
+ return log_error_errno(errno, "Failed to determine size of block device %s: %m", node);
+ } else {
+ r = stat_verify_regular(&st);
+ if (r < 0)
+ return log_error_errno(r, "Specified path '%s' is not a regular file or loopback block device, cannot resize: %m", node);
+
+ assert(!backing_file);
+ assert(!loop_device);
+ current_size = st.st_size;
+ }
+
+ assert_se(format_bytes(buf1, sizeof(buf1), current_size));
assert_se(format_bytes(buf2, sizeof(buf2), arg_size));
- if ((uint64_t) st.st_size >= arg_size) {
+ if (current_size >= arg_size) {
log_info("File '%s' already is of requested size or larger, not growing. (%s >= %s)", node, buf1, buf2);
return 0;
}
- /* The file descriptor is read-only. In order to grow the file we need to have a writable fd. We
- * reopen the file for that temporarily. We keep the writable fd only open for this operation though,
- * as fdisk can't accept it anyway. */
+ if (S_ISBLK(st.st_mode)) {
+ assert(backing_file);
+
+ /* This is a loopback device. We can't really grow those directly, but we can grow the
+ * backing file, hence let's do that. */
+
+ writable_fd = open(backing_file, O_WRONLY|O_CLOEXEC|O_NONBLOCK);
+ if (writable_fd < 0)
+ return log_error_errno(errno, "Failed to open backing file '%s': %m", backing_file);
+
+ if (fstat(writable_fd, &st) < 0)
+ return log_error_errno(errno, "Failed to stat() backing file '%s': %m", backing_file);
+
+ r = stat_verify_regular(&st);
+ if (r < 0)
+ return log_error_errno(r, "Backing file '%s' of block device is not a regular file: %m", backing_file);
+
+ if ((uint64_t) st.st_size != current_size)
+ return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+ "Size of backing file '%s' of loopback block device '%s' don't match, refusing.", node, backing_file);
+ } else {
+ assert(S_ISREG(st.st_mode));
+ assert(!backing_file);
- writable_fd = fd_reopen(*fd, O_WRONLY|O_CLOEXEC);
- if (writable_fd < 0)
- return log_error_errno(writable_fd, "Failed to reopen backing file '%s' writable: %m", node);
+ /* The file descriptor is read-only. In order to grow the file we need to have a writable fd. We
+ * reopen the file for that temporarily. We keep the writable fd only open for this operation though,
+ * as fdisk can't accept it anyway. */
+
+ writable_fd = fd_reopen(*fd, O_WRONLY|O_CLOEXEC);
+ if (writable_fd < 0)
+ return log_error_errno(writable_fd, "Failed to reopen backing file '%s' writable: %m", node);
+ }
if (!arg_discard) {
if (fallocate(writable_fd, 0, 0, arg_size) < 0) {
/* Fallback to truncation, if fallocate() is not supported. */
log_debug("Backing file system does not support fallocate(), falling back to ftruncate().");
} else {
- r = resize_pt(writable_fd);
- if (r < 0)
- return r;
-
- if (st.st_size == 0) /* Likely regular file just created by us */
+ if (current_size == 0) /* Likely regular file just created by us */
log_info("Allocated %s for '%s'.", buf2, node);
else
log_info("File '%s' grown from %s to %s by allocation.", node, buf1, buf2);
- return 1;
+ goto done;
}
}
return log_error_errno(errno, "Failed to grow '%s' from %s to %s by truncation: %m",
node, buf1, buf2);
+ if (current_size == 0) /* Likely regular file just created by us */
+ log_info("Sized '%s' to %s.", node, buf2);
+ else
+ log_info("File '%s' grown from %s to %s by truncation.", node, buf1, buf2);
+
+done:
r = resize_pt(writable_fd);
if (r < 0)
return r;
- if (st.st_size == 0) /* Likely regular file just created by us */
- log_info("Sized '%s' to %s.", node, buf2);
- else
- log_info("File '%s' grown from %s to %s by truncation.", node, buf1, buf2);
+ if (loop_device) {
+ r = loop_device_refresh_size(loop_device, UINT64_MAX, arg_size);
+ if (r < 0)
+ return log_error_errno(r, "Failed to update loop device size: %m");
+ }
return 1;
}
}
static int run(int argc, char *argv[]) {
+ _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL;
+ _cleanup_(decrypted_image_unrefp) DecryptedImage *decrypted_image = NULL;
+ _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL;
_cleanup_(context_freep) Context* context = NULL;
_cleanup_free_ char *node = NULL;
_cleanup_close_ int backing_fd = -1;
- bool from_scratch;
+ bool from_scratch, node_is_our_loop = false;
int r;
log_show_color(true);
log_parse_environment();
log_open();
- if (in_initrd()) {
- /* Default to operation on /sysroot when invoked in the initrd! */
- arg_root = strdup("/sysroot");
- if (!arg_root)
- return log_oom();
- }
-
r = parse_argv(argc, argv);
if (r <= 0)
return r;
if (r < 0)
return r;
+ if (arg_image) {
+ assert(!arg_root);
+
+ /* Mount this strictly read-only: we shall modify the partition table, not the file
+ * systems */
+ r = mount_image_privately_interactively(
+ arg_image,
+ DISSECT_IMAGE_MOUNT_READ_ONLY |
+ (arg_node ? DISSECT_IMAGE_DEVICE_READ_ONLY : 0) | /* If a different node to make changes to is specified let's open the device in read-only mode) */
+ DISSECT_IMAGE_GPT_ONLY |
+ DISSECT_IMAGE_RELAX_VAR_CHECK |
+ DISSECT_IMAGE_USR_NO_ROOT |
+ DISSECT_IMAGE_REQUIRE_ROOT,
+ &mounted_dir,
+ &loop_device,
+ &decrypted_image);
+ if (r < 0)
+ return r;
+
+ arg_root = strdup(mounted_dir);
+ if (!arg_root)
+ return log_oom();
+
+ if (!arg_node) {
+ arg_node = strdup(loop_device->node);
+ if (!arg_node)
+ return log_oom();
+
+ /* Remember that the the device we are about to manipulate is actually the one we
+ * allocated here, and thus to increase its backing file we know what to do */
+ node_is_our_loop = true;
+ }
+ }
+
context = context_new(arg_seed);
if (!context)
return log_oom();
return r;
if (arg_size != UINT64_MAX) {
- r = resize_backing_fd(node, &backing_fd);
+ r = resize_backing_fd(
+ node,
+ &backing_fd,
+ node_is_our_loop ? arg_image : NULL,
+ node_is_our_loop ? loop_device : NULL);
if (r < 0)
return r;
}
context_unload_partition_table(context);
assert_se(arg_size != UINT64_MAX);
- r = resize_backing_fd(node, &backing_fd);
+ r = resize_backing_fd(
+ node,
+ &backing_fd,
+ node_is_our_loop ? arg_image : NULL,
+ node_is_our_loop ? loop_device : NULL);
if (r < 0)
return r;