]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
repart: Add --copy-from option 28632/head
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Tue, 1 Aug 2023 19:38:39 +0000 (21:38 +0200)
committerDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 3 Aug 2023 09:12:25 +0000 (11:12 +0200)
--copy-from synthesizes partition definitions from the given image
which are then applied to the repart algorithm. In its most basic
form, this allows copying an image to another device but it can
also be combined with --definitions to copy + add partitions in the
same call to repart.

man/systemd-repart.xml
src/partition/repart.c
test/units/testsuite-58.sh

index 1799961527dc79aa30e222282a59ff94ac9818f4..06bccad8c32f829ed0a5be445c3f63fa812a8422 100644 (file)
         due to missing permissions.</para></listitem>
       </varlistentry>
 
+      <varlistentry>
+        <term><option>--copy-from=</option><arg>IMAGE</arg></term>
+
+        <listitem><para>Instructs <command>systemd-repart</command> to copy the partitions from the given
+        image. The partitions from the given image are synthesized into partition definitions that are
+        parsed before the partition definition files.</para></listitem>
+      </varlistentry>
+
       <xi:include href="standard-options.xml" xpointer="help" />
       <xi:include href="standard-options.xml" xpointer="version" />
       <xi:include href="standard-options.xml" xpointer="no-pager" />
index 40ce4356203ad7fd5bed759c9fe8488b6b322125..4fe2c63a46339be548016a9f74568b6afee793e3 100644 (file)
@@ -153,6 +153,7 @@ static uint64_t arg_sector_size = 0;
 static ImagePolicy *arg_image_policy = NULL;
 static Architecture arg_architecture = _ARCHITECTURE_INVALID;
 static int arg_offline = -1;
+static char *arg_copy_from = NULL;
 
 STATIC_DESTRUCTOR_REGISTER(arg_root, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image, freep);
@@ -164,6 +165,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_filter_partitions, freep);
 STATIC_DESTRUCTOR_REGISTER(arg_image_policy, image_policy_freep);
+STATIC_DESTRUCTOR_REGISTER(arg_copy_from, freep);
 
 typedef struct FreeArea FreeArea;
 
@@ -228,6 +230,7 @@ typedef struct Partition {
         bool copy_blocks_auto;
         const char *copy_blocks_root;
         int copy_blocks_fd;
+        uint64_t copy_blocks_offset;
         uint64_t copy_blocks_size;
 
         char *format;
@@ -346,6 +349,7 @@ static Partition *partition_new(void) {
                 .partno = UINT64_MAX,
                 .offset = UINT64_MAX,
                 .copy_blocks_fd = -EBADF,
+                .copy_blocks_offset = UINT64_MAX,
                 .copy_blocks_size = UINT64_MAX,
                 .no_auto = -1,
                 .read_only = -1,
@@ -1801,9 +1805,231 @@ static int find_verity_sibling(Context *context, Partition *p, VerityMode mode,
         return 0;
 }
 
+static int context_open_and_lock_backing_fd(const char *node, int *backing_fd) {
+        _cleanup_close_ int fd = -EBADF;
+
+        assert(node);
+        assert(backing_fd);
+
+        if (*backing_fd >= 0)
+                return 0;
+
+        fd = open(node, O_RDONLY|O_CLOEXEC);
+        if (fd < 0)
+                return log_error_errno(errno, "Failed to open device '%s': %m", node);
+
+        /* Tell udev not to interfere while we are processing the device */
+        if (flock(fd, arg_dry_run ? LOCK_SH : LOCK_EX) < 0)
+                return log_error_errno(errno, "Failed to lock device '%s': %m", node);
+
+        log_debug("Device %s opened and locked.", node);
+        *backing_fd = TAKE_FD(fd);
+        return 1;
+}
+
+static int determine_current_padding(
+                struct fdisk_context *c,
+                struct fdisk_table *t,
+                struct fdisk_partition *p,
+                uint64_t secsz,
+                uint64_t grainsz,
+                uint64_t *ret) {
+
+        size_t n_partitions;
+        uint64_t offset, next = UINT64_MAX;
+
+        assert(c);
+        assert(t);
+        assert(p);
+        assert(ret);
+
+        if (!fdisk_partition_has_end(p))
+                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition has no end!");
+
+        offset = fdisk_partition_get_end(p);
+        assert(offset < UINT64_MAX);
+        offset++; /* The end is one sector before the next partition or padding. */
+        assert(offset < UINT64_MAX / secsz);
+        offset *= secsz;
+
+        n_partitions = fdisk_table_get_nents(t);
+        for (size_t i = 0; i < n_partitions; i++) {
+                struct fdisk_partition *q;
+                uint64_t start;
+
+                q = fdisk_table_get_partition(t, i);
+                if (!q)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m");
+
+                if (fdisk_partition_is_used(q) <= 0)
+                        continue;
+
+                if (!fdisk_partition_has_start(q))
+                        continue;
+
+                start = fdisk_partition_get_start(q);
+                assert(start < UINT64_MAX / secsz);
+                start *= secsz;
+
+                if (start >= offset && (next == UINT64_MAX || next > start))
+                        next = start;
+        }
+
+        if (next == UINT64_MAX) {
+                /* No later partition? In that case check the end of the usable area */
+                next = fdisk_get_last_lba(c);
+                assert(next < UINT64_MAX);
+                next++; /* The last LBA is one sector before the end */
+
+                assert(next < UINT64_MAX / secsz);
+                next *= secsz;
+
+                if (offset > next)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition end beyond disk end.");
+        }
+
+        assert(next >= offset);
+        offset = round_up_size(offset, grainsz);
+        next = round_down_size(next, grainsz);
+
+        *ret = LESS_BY(next, offset); /* Saturated subtraction, rounding might have fucked things up */
+        return 0;
+}
+
+static int context_copy_from(Context *context) {
+        _cleanup_close_ int fd = -EBADF;
+        _cleanup_(fdisk_unref_contextp) struct fdisk_context *c = NULL;
+        _cleanup_(fdisk_unref_tablep) struct fdisk_table *t = NULL;
+        Partition *last = NULL;
+        unsigned long secsz, grainsz;
+        size_t n_partitions;
+        int r;
+
+        if (!arg_copy_from)
+                return 0;
+
+        r = context_open_and_lock_backing_fd(arg_copy_from, &fd);
+        if (r < 0)
+                return r;
+
+        r = fd_verify_regular(fd);
+        if (r < 0)
+                return log_error_errno(r, "%s is not a file: %m", arg_copy_from);
+
+        r = fdisk_new_context_fd(fd, /* read_only = */ true, /* sector_size = */ UINT32_MAX, &c);
+        if (r < 0)
+                return log_error_errno(r, "Failed to create fdisk context: %m");
+
+        secsz = fdisk_get_sector_size(c);
+        grainsz = fdisk_get_grain_size(c);
+
+        /* Insist on a power of two, and that it's a multiple of 512, i.e. the traditional sector size. */
+        if (secsz < 512 || !ISPOWEROF2(secsz))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Sector size %lu is not a power of two larger than 512? Refusing.", secsz);
+
+        if (!fdisk_is_labeltype(c, FDISK_DISKLABEL_GPT))
+                return log_error_errno(SYNTHETIC_ERRNO(EHWPOISON), "Cannot copy from disk %s with no GPT disk label.", arg_copy_from);
+
+        r = fdisk_get_partitions(c, &t);
+        if (r < 0)
+                return log_error_errno(r, "Failed to acquire partition table: %m");
+
+        n_partitions = fdisk_table_get_nents(t);
+        for (size_t i = 0; i < n_partitions; i++) {
+                _cleanup_(partition_freep) Partition *np = NULL;
+                _cleanup_free_ char *label_copy = NULL;
+                struct fdisk_partition *p;
+                const char *label;
+                uint64_t sz, start, padding;
+                sd_id128_t ptid, id;
+                GptPartitionType type;
+
+                p = fdisk_table_get_partition(t, i);
+                if (!p)
+                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m");
+
+                if (fdisk_partition_is_used(p) <= 0)
+                        continue;
+
+                if (fdisk_partition_has_start(p) <= 0 ||
+                    fdisk_partition_has_size(p) <= 0 ||
+                    fdisk_partition_has_partno(p) <= 0)
+                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Found a partition without a position, size or number.");
+
+                r = fdisk_partition_get_type_as_id128(p, &ptid);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to query partition type UUID: %m");
+
+                type = gpt_partition_type_from_uuid(ptid);
+
+                r = fdisk_partition_get_uuid_as_id128(p, &id);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to query partition UUID: %m");
+
+                label = fdisk_partition_get_name(p);
+                if (!isempty(label)) {
+                        label_copy = strdup(label);
+                        if (!label_copy)
+                                return log_oom();
+                }
+
+                sz = fdisk_partition_get_size(p);
+                assert(sz <= UINT64_MAX/secsz);
+                sz *= secsz;
+
+                start = fdisk_partition_get_start(p);
+                assert(start <= UINT64_MAX/secsz);
+                start *= secsz;
+
+                if (partition_type_exclude(type))
+                        continue;
+
+                np = partition_new();
+                if (!np)
+                        return log_oom();
+
+                np->type = type;
+                np->new_uuid = id;
+                np->new_uuid_is_set = true;
+                np->size_min = np->size_max = sz;
+                np->new_label = TAKE_PTR(label_copy);
+
+                np->definition_path = strdup(arg_copy_from);
+                if (!np->definition_path)
+                        return log_oom();
+
+                r = determine_current_padding(c, t, p, secsz, grainsz, &padding);
+                if (r < 0)
+                        return r;
+
+                np->padding_min = np->padding_max = padding;
+
+                np->copy_blocks_path = strdup(arg_copy_from);
+                if (!np->copy_blocks_path)
+                        return log_oom();
+
+                np->copy_blocks_fd = fcntl(fd, F_DUPFD_CLOEXEC, 3);
+                if (np->copy_blocks_fd < 0)
+                        return log_error_errno(r, "Failed to duplicate file descriptor of %s: %m", arg_copy_from);
+
+                np->copy_blocks_offset = start;
+                np->copy_blocks_size = sz;
+
+                r = fdisk_partition_get_attrs_as_uint64(p, &np->gpt_flags);
+                if (r < 0)
+                        return log_error_errno(r, "Failed to get partition flags: %m");
+
+                LIST_INSERT_AFTER(partitions, context->partitions, last, np);
+                last = TAKE_PTR(np);
+                context->n_partitions++;
+        }
+
+        return 0;
+}
+
 static int context_read_definitions(Context *context) {
         _cleanup_strv_free_ char **files = NULL;
-        Partition *last = NULL;
+        Partition *last = LIST_FIND_TAIL(partitions, context->partitions);
         const char *const *dirs;
         int r;
 
@@ -1899,74 +2125,6 @@ static int context_read_definitions(Context *context) {
         return 0;
 }
 
-static int determine_current_padding(
-                struct fdisk_context *c,
-                struct fdisk_table *t,
-                struct fdisk_partition *p,
-                uint64_t secsz,
-                uint64_t grainsz,
-                uint64_t *ret) {
-
-        size_t n_partitions;
-        uint64_t offset, next = UINT64_MAX;
-
-        assert(c);
-        assert(t);
-        assert(p);
-
-        if (!fdisk_partition_has_end(p))
-                return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition has no end!");
-
-        offset = fdisk_partition_get_end(p);
-        assert(offset < UINT64_MAX);
-        offset++; /* The end is one sector before the next partition or padding. */
-        assert(offset < UINT64_MAX / secsz);
-        offset *= secsz;
-
-        n_partitions = fdisk_table_get_nents(t);
-        for (size_t i = 0; i < n_partitions; i++) {
-                struct fdisk_partition *q;
-                uint64_t start;
-
-                q = fdisk_table_get_partition(t, i);
-                if (!q)
-                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to read partition metadata: %m");
-
-                if (fdisk_partition_is_used(q) <= 0)
-                        continue;
-
-                if (!fdisk_partition_has_start(q))
-                        continue;
-
-                start = fdisk_partition_get_start(q);
-                assert(start < UINT64_MAX / secsz);
-                start *= secsz;
-
-                if (start >= offset && (next == UINT64_MAX || next > start))
-                        next = start;
-        }
-
-        if (next == UINT64_MAX) {
-                /* No later partition? In that case check the end of the usable area */
-                next = fdisk_get_last_lba(c);
-                assert(next < UINT64_MAX);
-                next++; /* The last LBA is one sector before the end */
-
-                assert(next < UINT64_MAX / secsz);
-                next *= secsz;
-
-                if (offset > next)
-                        return log_error_errno(SYNTHETIC_ERRNO(EIO), "Partition end beyond disk end.");
-        }
-
-        assert(next >= offset);
-        offset = round_up_size(offset, grainsz);
-        next = round_down_size(next, grainsz);
-
-        *ret = LESS_BY(next, offset); /* Saturated subtraction, rounding might have fucked things up */
-        return 0;
-}
-
 static int fdisk_ask_cb(struct fdisk_context *c, struct fdisk_ask *ask, void *data) {
         _cleanup_free_ char *ids = NULL;
         int r;
@@ -2022,28 +2180,6 @@ static int derive_uuid(sd_id128_t base, const char *token, sd_id128_t *ret) {
         return 0;
 }
 
-static int context_open_and_lock_backing_fd(Context *context, const char *node) {
-        _cleanup_close_ int fd = -EBADF;
-
-        assert(context);
-        assert(node);
-
-        if (context->backing_fd >= 0)
-                return 0;
-
-        fd = open(node, O_RDONLY|O_CLOEXEC);
-        if (fd < 0)
-                return log_error_errno(errno, "Failed to open device '%s': %m", node);
-
-        /* Tell udev not to interfere while we are processing the device */
-        if (flock(fd, arg_dry_run ? LOCK_SH : LOCK_EX) < 0)
-                return log_error_errno(errno, "Failed to lock device '%s': %m", node);
-
-        log_debug("Device %s opened and locked.", node);
-        context->backing_fd = TAKE_FD(fd);
-        return 1;
-}
-
 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;
@@ -2072,7 +2208,7 @@ static int context_load_partition_table(Context *context) {
         else {
                 uint32_t ssz;
 
-                r = context_open_and_lock_backing_fd(context, context->node);
+                r = context_open_and_lock_backing_fd(context->node, &context->backing_fd);
                 if (r < 0)
                         return r;
 
@@ -2119,7 +2255,7 @@ static int context_load_partition_table(Context *context) {
 
         if (context->backing_fd < 0) {
                 /* If we have no fd referencing the device yet, make a copy of the fd now, so that we have one */
-                r = context_open_and_lock_backing_fd(context, FORMAT_PROC_FD_PATH(fdisk_get_devfd(c)));
+                r = context_open_and_lock_backing_fd(FORMAT_PROC_FD_PATH(fdisk_get_devfd(c)), &context->backing_fd);
                 if (r < 0)
                         return r;
         }
@@ -3940,6 +4076,9 @@ static int context_copy_blocks(Context *context) {
                 log_info("Copying in '%s' (%s) on block level into future partition %" PRIu64 ".",
                          p->copy_blocks_path, FORMAT_BYTES(p->copy_blocks_size), p->partno);
 
+                if (p->copy_blocks_offset != UINT64_MAX && lseek(p->copy_blocks_fd, p->copy_blocks_offset, SEEK_SET) < 0)
+                        return log_error_errno(errno, "Failed to seek to copy blocks offset in %s: %m", p->copy_blocks_path);
+
                 r = copy_bytes(p->copy_blocks_fd, partition_target_fd(t), p->copy_blocks_size, COPY_REFLINK);
                 if (r < 0)
                         return log_error_errno(r, "Failed to copy in data from '%s': %m", p->copy_blocks_path);
@@ -5996,6 +6135,7 @@ static int help(void) {
                "     --sector-size=SIZE   Set the logical sector size for the image\n"
                "     --architecture=ARCH  Set the generic architecture for the image\n"
                "     --offline=BOOL       Whether to build the image offline\n"
+               "     --copy-from=IMAGE    Copy partitions from the given image\n"
                "\nSee the %s for details.\n",
                program_invocation_short_name,
                ansi_highlight(),
@@ -6039,6 +6179,7 @@ static int parse_argv(int argc, char *argv[]) {
                 ARG_SKIP_PARTITIONS,
                 ARG_ARCHITECTURE,
                 ARG_OFFLINE,
+                ARG_COPY_FROM,
         };
 
         static const struct option options[] = {
@@ -6073,6 +6214,7 @@ static int parse_argv(int argc, char *argv[]) {
                 { "sector-size",          required_argument, NULL, ARG_SECTOR_SIZE          },
                 { "architecture",         required_argument, NULL, ARG_ARCHITECTURE         },
                 { "offline",              required_argument, NULL, ARG_OFFLINE              },
+                { "copy-from",            required_argument, NULL, ARG_COPY_FROM            },
                 {}
         };
 
@@ -6394,6 +6536,12 @@ static int parse_argv(int argc, char *argv[]) {
 
                         break;
 
+                case ARG_COPY_FROM:
+                        r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_copy_from);
+                        if (r < 0)
+                                return r;
+                        break;
+
                 case '?':
                         return -EINVAL;
 
@@ -6945,6 +7093,10 @@ static int run(int argc, char *argv[]) {
         if (!context)
                 return log_oom();
 
+        r = context_copy_from(context);
+        if (r < 0)
+                return r;
+
         strv_uniq(arg_definitions);
 
         r = context_read_definitions(context);
index 13e40bd82ab9aa6290c73d16b4dfc65f2c9f48e3..118d797f2ba8bf73a75b3c0aa2fe2eeb31c367fe 100755 (executable)
@@ -160,6 +160,27 @@ last-lba: 2097118
 $imgs/zzz1 : start=        2048, size=     1775576, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
 $imgs/zzz2 : start=     1777624, size=      131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\""
 
+    systemd-repart --offline="$OFFLINE" \
+                   --empty=create \
+                   --size=1G \
+                   --dry-run=no \
+                   --seed="$seed" \
+                   --copy-from="$imgs/zzz" \
+                   "$imgs/copy"
+
+    output=$(sfdisk -d "$imgs/copy" | grep -v -e 'sector-size' -e '^$')
+
+    assert_eq "$output" "label: gpt
+label-id: 1D2CE291-7CCE-4F7D-BC83-FDB49AD74EBD
+device: $imgs/copy
+unit: sectors
+first-lba: 2048
+last-lba: 2097118
+$imgs/copy1 : start=        2048, size=     1775576, type=933AC7E1-2EB4-4F13-B844-0E14E2AEF915, uuid=4980595D-D74A-483A-AA9E-9903879A0EE5, name=\"home-first\", attrs=\"GUID:59\"
+$imgs/copy2 : start=     1777624, size=      131072, type=0657FD6D-A4AB-43C4-84E5-0933C84B4F4F, uuid=78C92DB8-3D2B-4823-B0DC-792B78F66F1E, name=\"swap\""
+
+    rm "$imgs/copy" # Save disk space
+
     systemd-repart --offline="$OFFLINE" \
                    --definitions="$defs" \
                    --dry-run=no \