]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: add --image= switch
authorLennart Poettering <lennart@poettering.net>
Fri, 19 Mar 2021 10:19:00 +0000 (11:19 +0100)
committerLennart Poettering <lennart@poettering.net>
Mon, 19 Apr 2021 21:16:02 +0000 (23:16 +0200)
This is similar to the --image= switch in the other tools, like
systemd-sysusers or systemd-tmpfiles, i.e. it apply the configuration
from the image to the image.

This is particularly useful for downloading minimized GPT image, and
then extending it to the desired size via:

   # systemd-repart --image=foo.image --size=5G

man/repart.d.xml
man/systemd-repart.xml
src/partition/repart.c

index b6346b3f8537c61f5d50d6b8c0d139bb0dd824ab..67f687947e4185e2a7ebc413e36136f12754be9a 100644 (file)
         <para>The copy operation is executed before the file system is registered in the partition table,
         thus ensuring that a file system populated this way only ever exists fully initialized.</para>
 
-        <para>This option cannot be combined with <varname>CopyBlocks=</varname>.</para></listitem>
+        <para>This option cannot be combined with <varname>CopyBlocks=</varname>.</para>
+
+        <para>When <command>systemd-repart</command> is invoked with the <option>--image=</option> or
+        <option>--root=</option> command line switches the source paths specified are taken relative to the
+        specified root directory or disk image root.</para></listitem>
       </varlistentry>
 
       <varlistentry>
index a5a0890c526c174f48ecb36fc324cabe06ed46dd..380ba57884a3da0e03230c2b4713f7d6c1e932f2 100644 (file)
     <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>
 
index 155be0610c0e333bcb98352ad4797893e300060b..081f95ebe70bd96c13511b47a3fd7e0e567ef90b 100644 (file)
@@ -92,6 +92,7 @@ static enum {
 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;
@@ -110,6 +111,7 @@ static char *arg_tpm2_device = NULL;
 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);
@@ -3483,6 +3485,7 @@ static int help(void) {
                "                          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"
@@ -3513,6 +3516,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_FACTORY_RESET,
                 ARG_CAN_FACTORY_RESET,
                 ARG_ROOT,
+                ARG_IMAGE,
                 ARG_SEED,
                 ARG_PRETTY,
                 ARG_DEFINITIONS,
@@ -3534,6 +3538,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "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       },
@@ -3613,7 +3618,13 @@ static int parse_argv(int argc, char *argv[]) {
                         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;
@@ -3763,9 +3774,18 @@ static int parse_argv(int argc, char *argv[]) {
                 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.");
 
@@ -3838,39 +3858,44 @@ static int remove_efi_variable_factory_reset(void) {
         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) {
@@ -3930,7 +3955,9 @@ static int find_root(char **ret, int *ret_fd) {
                         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)
@@ -3958,7 +3985,7 @@ static int find_root(char **ret, int *ret_fd) {
                 } 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);
@@ -4005,9 +4032,15 @@ static int resize_pt(int fd) {
         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;
 
@@ -4028,25 +4061,64 @@ static int resize_backing_fd(const char *node, int *fd) {
         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, &current_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) {
@@ -4057,16 +4129,12 @@ static int resize_backing_fd(const char *node, int *fd) {
                         /* 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;
                 }
         }
 
@@ -4074,14 +4142,21 @@ static int resize_backing_fd(const char *node, int *fd) {
                 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;
 }
@@ -4116,23 +4191,19 @@ static int determine_auto_size(Context *c) {
 }
 
 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;
@@ -4145,6 +4216,40 @@ static int run(int argc, char *argv[]) {
         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();
@@ -4163,7 +4268,11 @@ static int run(int argc, char *argv[]) {
                 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;
         }
@@ -4225,7 +4334,11 @@ static int run(int argc, char *argv[]) {
                 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;