From 56a279a7f718c5c572a9a26ac1a2d61923aefe04 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Thu, 28 Nov 2024 14:06:34 +0100 Subject: [PATCH 1/7] copy: add support for copying fs-verity status Add a new member to CopyFlags to request copying of fs-verity status. If copying of fs-verity is requested then we query the descriptor from each regular file. If it has one, we use it to setup fs-verity on the destination, using the same parameters. Signatures don't seem to be a particularly well-loved (or used) feature of fs-verity and we don't bother to query them here. Support for that could be added later, if desired. This change means that, with the correct combination of flags, we might end up calling `fsync()` on a read-only file descriptor. This is permitted by POSIX and supported on Linux. Nothing uses this yet. Signed-off-by: Allison Karlitskaya --- src/basic/include/linux/fsverity.h | 96 ++++++++++++++++++++++++++++++ src/shared/copy.c | 69 +++++++++++++++++++++ src/shared/copy.h | 1 + 3 files changed, 166 insertions(+) create mode 100644 src/basic/include/linux/fsverity.h diff --git a/src/basic/include/linux/fsverity.h b/src/basic/include/linux/fsverity.h new file mode 100644 index 0000000000..bdc2ca6639 --- /dev/null +++ b/src/basic/include/linux/fsverity.h @@ -0,0 +1,96 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* + * fs-verity user API + * + * These ioctls can be used on filesystems that support fs-verity. See the + * "User API" section of Documentation/filesystems/fsverity.rst. + * + * Copyright 2019 Google LLC + */ +#ifndef _LINUX_FSVERITY_H +#define _LINUX_FSVERITY_H + +#include +#include + +#define FS_VERITY_HASH_ALG_SHA256 1 +#define FS_VERITY_HASH_ALG_SHA512 2 + +struct fsverity_enable_arg { + __u32 version; + __u32 hash_algorithm; + __u32 block_size; + __u32 salt_size; + __u64 salt_ptr; + __u32 sig_size; + __u32 __reserved1; + __u64 sig_ptr; + __u64 __reserved2[11]; +}; + +struct fsverity_digest { + __u16 digest_algorithm; + __u16 digest_size; /* input/output */ + __u8 digest[]; +}; + +/* + * Struct containing a file's Merkle tree properties. The fs-verity file digest + * is the hash of this struct. A userspace program needs this struct only if it + * needs to compute fs-verity file digests itself, e.g. in order to sign files. + * It isn't needed just to enable fs-verity on a file. + * + * Note: when computing the file digest, 'sig_size' and 'signature' must be left + * zero and empty, respectively. These fields are present only because some + * filesystems reuse this struct as part of their on-disk format. + */ +struct fsverity_descriptor { + __u8 version; /* must be 1 */ + __u8 hash_algorithm; /* Merkle tree hash algorithm */ + __u8 log_blocksize; /* log2 of size of data and tree blocks */ + __u8 salt_size; /* size of salt in bytes; 0 if none */ + __le32 __reserved_0x04; /* must be 0 */ + __le64 data_size; /* size of file the Merkle tree is built over */ + __u8 root_hash[64]; /* Merkle tree root hash */ + __u8 salt[32]; /* salt prepended to each hashed block */ + __u8 __reserved[144]; /* must be 0's */ +}; + +/* + * Format in which fs-verity file digests are signed in built-in signatures. + * This is the same as 'struct fsverity_digest', except here some magic bytes + * are prepended to provide some context about what is being signed in case the + * same key is used for non-fsverity purposes, and here the fields have fixed + * endianness. + * + * This struct is specific to the built-in signature verification support, which + * is optional. fs-verity users may also verify signatures in userspace, in + * which case userspace is responsible for deciding on what bytes are signed. + * This struct may still be used, but it doesn't have to be. For example, + * userspace could instead use a string like "sha256:$digest_as_hex_string". + */ +struct fsverity_formatted_digest { + char magic[8]; /* must be "FSVerity" */ + __le16 digest_algorithm; + __le16 digest_size; + __u8 digest[]; +}; + +#define FS_VERITY_METADATA_TYPE_MERKLE_TREE 1 +#define FS_VERITY_METADATA_TYPE_DESCRIPTOR 2 +#define FS_VERITY_METADATA_TYPE_SIGNATURE 3 + +struct fsverity_read_metadata_arg { + __u64 metadata_type; + __u64 offset; + __u64 length; + __u64 buf_ptr; + __u64 __reserved; +}; + +#define FS_IOC_ENABLE_VERITY _IOW('f', 133, struct fsverity_enable_arg) +#define FS_IOC_MEASURE_VERITY _IOWR('f', 134, struct fsverity_digest) +#define FS_IOC_READ_VERITY_METADATA \ + _IOWR('f', 135, struct fsverity_read_metadata_arg) + +#endif /* _LINUX_FSVERITY_H */ diff --git a/src/shared/copy.c b/src/shared/copy.c index ca2325459d..0c79121b68 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -2,6 +2,8 @@ #include #include +#include +#include #include #include #include @@ -790,6 +792,64 @@ static int prepare_nocow(int fdf, const char *from, int fdt, unsigned *chattr_ma return 0; } +/* Copies fs-verity status. May re-open fdt to do its job. */ +static int copy_fs_verity(int fdf, int *fdt) { + int r; + + assert(fdf >= 0); + assert(fdt); + assert(*fdt >= 0); + + r = fd_verify_regular(fdf); + if (r < 0) + return r; + + struct fsverity_descriptor desc = {}; + struct fsverity_read_metadata_arg read_arg = { + .metadata_type = FS_VERITY_METADATA_TYPE_DESCRIPTOR, + .buf_ptr = (uintptr_t) &desc, + .length = sizeof(desc), + }; + + r = ioctl(fdf, FS_IOC_READ_VERITY_METADATA, &read_arg); + if (r < 0) { + /* ENODATA means that the file doesn't have fs-verity, + * so the correct thing to do is to do nothing at all. */ + if (errno == ENODATA) + return 0; + return log_error_errno(errno, "Failed to read fs-verity metadata from source file: %m"); + } + + /* Make sure that the descriptor is completely initialized */ + assert(r == (int) sizeof desc); + + r = fd_verify_regular(*fdt); + if (r < 0) + return r; + + /* Okay. We're doing this now. We need to re-open fdt as read-only because + * we can't enable fs-verity while writable file descriptors are outstanding. */ + _cleanup_close_ int reopened_fd = -EBADF; + r = fd_reopen_condition(*fdt, O_RDONLY|O_CLOEXEC|O_NOCTTY, O_ACCMODE_STRICT|O_PATH, &reopened_fd); + if (r < 0) + return r; + if (reopened_fd >= 0) + close_and_replace(*fdt, reopened_fd); + + struct fsverity_enable_arg enable_arg = { + .version = desc.version, + .hash_algorithm = desc.hash_algorithm, + .block_size = UINT32_C(1) << desc.log_blocksize, + .salt_size = desc.salt_size, + .salt_ptr = (uintptr_t) &desc.salt, + }; + + if (ioctl(*fdt, FS_IOC_ENABLE_VERITY, &enable_arg) < 0) + return log_error_errno(errno, "Failed to set fs-verity metadata: %m"); + + return 0; +} + static int fd_copy_tree_generic( int df, const char *from, @@ -874,6 +934,15 @@ static int fd_copy_regular( return r; } + /* NB: fs-verity cannot be enabled when a writable file descriptor is outstanding. + * copy_fs_verity() may well re-open 'fdt' as O_RDONLY. All code below this point + * needs to be able to work with a read-only file descriptor. */ + if (FLAGS_SET(copy_flags, COPY_PRESERVE_FS_VERITY)) { + r = copy_fs_verity(fdf, &fdt); + if (r < 0) + goto fail; + } + if (copy_flags & COPY_FSYNC) { if (fsync(fdt) < 0) { r = -errno; diff --git a/src/shared/copy.h b/src/shared/copy.h index 3efe877c4b..1d17f9b01f 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -32,6 +32,7 @@ typedef enum CopyFlags { * copy because reflinking from COW to NOCOW files is not supported. */ COPY_NOCOW_AFTER = 1 << 20, + COPY_PRESERVE_FS_VERITY = 1 << 21, /* Preserve fs-verity when copying. */ } CopyFlags; typedef enum DenyType { From fdc5f9e84404449129c63788c93b27444fca4372 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Mon, 26 May 2025 13:02:33 +0200 Subject: [PATCH 2/7] test: add a testcase for copy_tree() and fs-verity --- src/test/test-copy.c | 118 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/src/test/test-copy.c b/src/test/test-copy.c index 7204779041..9da7778655 100644 --- a/src/test/test-copy.c +++ b/src/test/test-copy.c @@ -1,6 +1,8 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#include #include +#include #include #include @@ -591,4 +593,120 @@ TEST(copy_verify_linked) { assert_se(faccessat(tfd, "to_2", F_OK, AT_SYMLINK_NOFOLLOW) < 0 && errno == ENOENT); } +static bool enable_fsverity(int dirfd, const char *name, int algo, const char *salt, size_t salt_size) { + _cleanup_close_ int fd = -EBADF; + ASSERT_OK_ERRNO(fd = openat(dirfd, name, O_RDONLY | O_CLOEXEC | O_NOCTTY)); + + struct fsverity_enable_arg enable_arg = { + .version = 1, + .hash_algorithm = algo, + .block_size = 4096, + .salt_size = salt_size, + .salt_ptr = (uintptr_t) salt + }; + + if (ioctl(fd, FS_IOC_ENABLE_VERITY, &enable_arg) == 0) + return true; + + /* We might get this if we're on a filesystem without support. */ + ASSERT_TRUE(IN_SET(errno, EOPNOTSUPP, ENOTTY)); + return false; +} + +static struct fsverity_digest* alloc_fsverity_digest(size_t digest_size) { + struct fsverity_digest *digest = malloc(sizeof (struct fsverity_digest) + digest_size); + ASSERT_NOT_NULL(digest); + digest->digest_size = digest_size; + return digest; +} + +static bool measure_fsverity(int dirfd, const char *name, struct fsverity_digest *digest) { + _cleanup_close_ int fd = -EBADF; + ASSERT_OK_ERRNO(fd = openat(dirfd, name, O_RDONLY | O_CLOEXEC | O_NOCTTY)); + if (ioctl(fd, FS_IOC_MEASURE_VERITY, digest) == 0) + return true; + + /* Make sure that the error is caused by the file simply not having fs-verity. */ + ASSERT_TRUE(errno == ENODATA); + return false; +} + +static void assert_no_fsverity(int dirfd, const char *name) { + _cleanup_free_ struct fsverity_digest *digest = alloc_fsverity_digest(0); + ASSERT_FALSE(measure_fsverity(dirfd, name, digest)); +} + +static void assert_fsverity_eq(int dirfd_a, int dirfd_b, const char *name) { + _cleanup_free_ struct fsverity_digest *digest_a = alloc_fsverity_digest(64); + _cleanup_free_ struct fsverity_digest *digest_b = alloc_fsverity_digest(64); + + bool enabled_a = measure_fsverity(dirfd_a, name, digest_a); + bool enabled_b = measure_fsverity(dirfd_b, name, digest_b); + ASSERT_EQ(enabled_a, enabled_b); + if (enabled_a) { + ASSERT_EQ(digest_a->digest_algorithm, digest_b->digest_algorithm); + ASSERT_EQ(digest_a->digest_size, digest_b->digest_size); + ASSERT_EQ(memcmp(digest_a->digest, digest_b->digest, digest_a->digest_size), 0); + } +} + +TEST_RET(copy_with_verity) { + _cleanup_(rm_rf_physical_and_freep) char *srcp = NULL, *dstp = NULL, *badsrcp = NULL, *baddstp = NULL; + const char *files[] = { "disabled", "simple", "bigsha", "salty" }; + _cleanup_close_ int src = -EBADF, dst = -EBADF, badsrc = -EBADF, baddst = -EBADF; + + /* We're more likely to hit a filesystem with fs-verity enabled on /var/tmp than on /tmp (tmpfs) */ + ASSERT_OK(src = mkdtemp_open("/var/tmp/test-copy_file-src.XXXXXX", 0, &srcp)); + ASSERT_OK(dst = mkdtemp_open("/var/tmp/test-copy_file-dst.XXXXXX", 0, &dstp)); + + /* Populate some data to differentiate the files. */ + FOREACH_ELEMENT(file, files) + ASSERT_OK(write_string_file_at(src, *file, "src file", WRITE_STRING_FILE_CREATE)); + + /* Enable on some file using a range of options */ + if (!enable_fsverity(src, "simple", FS_VERITY_HASH_ALG_SHA256, NULL, 0)) + return log_tests_skipped_errno(errno, "/var/tmp: fs-verity is not supported here"); + ASSERT_TRUE(enable_fsverity(src, "bigsha", FS_VERITY_HASH_ALG_SHA512, NULL, 0)); + ASSERT_TRUE(enable_fsverity(src, "salty", FS_VERITY_HASH_ALG_SHA512, "edamame", 8)); + + /* Copy without fs-verity enabled and make sure nothing is set on the destination */ + ASSERT_OK(copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REPLACE|COPY_MERGE, NULL, NULL)); + FOREACH_ELEMENT(file, files) + assert_no_fsverity(dst, *file); + + /* Copy *with* fs-verity enabled and make sure it works properly */ + int r = copy_tree_at(src, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL); + if (r == -ENOTTY) + /* This can happen on some versions of btrfs, for example */ + return log_tests_skipped_errno(errno, "/var/tmp: fs-verity supported, but not reading metadata"); + ASSERT_OK(r); + FOREACH_ELEMENT(file, files) + assert_fsverity_eq(src, dst, *file); + + /* Now try to create files where we know fs-verity doesn't work: tmpfs */ + ASSERT_OK(badsrc = mkdtemp_open("/tmp/test-copy_file-src.XXXXXX", 0, &badsrcp)); + ASSERT_OK(baddst = mkdtemp_open("/tmp/test-copy_file-src.XXXXXX", 0, &baddstp)); + + /* Populate the source, same as before */ + FOREACH_ELEMENT(file, files) + ASSERT_OK(write_string_file_at(badsrc, *file, "src file", WRITE_STRING_FILE_CREATE)); + + /* Ensure the attempting to enable fs-verity here will fail */ + if (enable_fsverity(badsrc, "simple", FS_VERITY_HASH_ALG_SHA256, NULL, 0)) + return log_tests_skipped_errno(errno, "/tmp: fs-verity *is* unexpectedly supported here"); + + /* Copy from our non-verity filesystem into dst, requesting verity and making sure we notice that + * we failed to read verity from the source. */ + ASSERT_ERROR(copy_tree_at(badsrc, ".", dst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL), ENOTTY); + + /* Copy from our verity filesystem into our baddst, requesting verity and making sure we notice that + * we failed to set verity on the destination. */ + ASSERT_ERROR(copy_tree_at(src, ".", baddst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL), ENOTTY); + + /* Of course this should fail too... */ + ASSERT_ERROR(copy_tree_at(badsrc, ".", baddst, ".", UID_INVALID, GID_INVALID, COPY_REFLINK|COPY_REPLACE|COPY_MERGE|COPY_PRESERVE_FS_VERITY, NULL, NULL), ENOTTY); + + return 0; +} + DEFINE_TEST_MAIN(LOG_DEBUG); From c69598a3abaacb0a5e79179aa19157c58fdc89d8 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Wed, 19 Mar 2025 15:44:51 +0100 Subject: [PATCH 3/7] mkfs-util: rename a local variable Change 'flags' to 'fork_flags' in preparation for using 'flags' as an argument in the next commit. Signed-off-by: Allison Karlitskaya --- src/shared/mkfs-util.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index d0ecb391d6..5412a60902 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -335,7 +335,7 @@ int make_filesystem( _cleanup_(unlink_and_freep) char *protofile = NULL; char vol_id[CONST_MAX(SD_ID128_UUID_STRING_MAX, 8U + 1U)] = {}; int stdio_fds[3] = { -EBADF, STDERR_FILENO, STDERR_FILENO}; - ForkFlags flags = FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT| + ForkFlags fork_flags = FORK_RESET_SIGNALS|FORK_RLIMIT_NOFILE_SAFE|FORK_DEATHSIG_SIGTERM|FORK_LOG|FORK_WAIT| FORK_CLOSE_ALL_FDS|FORK_REARRANGE_STDIO|FORK_REOPEN_LOG; int r; @@ -663,7 +663,7 @@ int make_filesystem( return log_error_errno(r, "Failed to stat '%s': %m", node); if (S_ISBLK(st.st_mode)) - flags |= FORK_NEW_MOUNTNS; + fork_flags |= FORK_NEW_MOUNTNS; } if (DEBUG_LOGGING) { @@ -678,7 +678,7 @@ int make_filesystem( stdio_fds, /*except_fds=*/ NULL, /*n_except_fds=*/ 0, - flags, + fork_flags, /*ret_pid=*/ NULL); if (r < 0) return r; @@ -695,7 +695,7 @@ int make_filesystem( * on unformatted free space, so let's trick it and other mkfs tools into thinking no * partitions are mounted. See https://github.com/kdave/btrfs-progs/issues/640 for more ° information. */ - if (flags & FORK_NEW_MOUNTNS) + if (fork_flags & FORK_NEW_MOUNTNS) (void) mount_nofollow_verbose(LOG_DEBUG, "/dev/null", "/proc/self/mounts", NULL, MS_BIND, NULL); execvp(mkfs, argv); From d89283b48e72091c2b787cbbd796e75a1390bb33 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Tue, 3 Dec 2024 09:21:16 +0100 Subject: [PATCH 4/7] mkfs-util: turn quiet/discard to a flags field Add a new `MakeFilesystemFlags` enum and use it to replace the existing `quiet` and `discard` booleans on `make_filesystem()`. Update the callers. While we're doing that, consolidate some duplicated logic in systemd-repart. Signed-off-by: Allison Karlitskaya --- src/growfs/makefs.c | 3 +-- src/home/homework-luks.c | 3 +-- src/repart/repart.c | 21 +++++++++++++++------ src/shared/mkfs-util.c | 29 ++++++++++++++--------------- src/shared/mkfs-util.h | 8 ++++++-- src/test/test-loop-block.c | 8 ++++---- 6 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/growfs/makefs.c b/src/growfs/makefs.c index 3378bd3d70..897b9a7600 100644 --- a/src/growfs/makefs.c +++ b/src/growfs/makefs.c @@ -70,8 +70,7 @@ static int run(int argc, char *argv[]) { label, /* root = */ NULL, uuid, - /* discard = */ true, - /* quiet = */ true, + MKFS_DISCARD | MKFS_QUIET, /* sector_size = */ 0, /* compression = */ NULL, /* compression_level = */ NULL, diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c index e92e5e2eb9..01ac02b40a 100644 --- a/src/home/homework-luks.c +++ b/src/home/homework-luks.c @@ -2382,8 +2382,7 @@ int home_create_luks( user_record_user_name_and_realm(h), /* root = */ NULL, fs_uuid, - user_record_luks_discard(h), - /* quiet = */ true, + (user_record_luks_discard(h) ? MKFS_DISCARD : 0) | MKFS_QUIET, /* sector_size = */ 0, /* compression = */ NULL, /* compression_level= */ NULL, diff --git a/src/repart/repart.c b/src/repart/repart.c index 89d4297d62..d46caf39af 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -2365,6 +2365,18 @@ static bool partition_needs_populate(const Partition *p) { (p->suppressing && partition_needs_populate(p->suppressing)); } +static MakeFileSystemFlags partition_mkfs_flags(const Partition *p) { + MakeFileSystemFlags flags = 0; + + if (arg_discard) + flags |= MKFS_DISCARD; + + if (streq(p->format, "erofs") && !DEBUG_LOGGING) + flags |= MKFS_QUIET; + + return flags; +} + static int partition_read_definition(Partition *p, const char *path, const char *const *conf_file_dirs) { ConfigTableItem table[] = { @@ -6254,8 +6266,7 @@ static int context_mkfs(Context *context) { return r; r = make_filesystem(partition_target_path(t), p->format, strempty(p->new_label), root, - p->fs_uuid, arg_discard, - /* quiet = */ streq(p->format, "erofs") && !DEBUG_LOGGING, + p->fs_uuid, partition_mkfs_flags(p), context->fs_sector_size, p->compression, p->compression_level, extra_mkfs_options); if (r < 0) @@ -7849,8 +7860,7 @@ static int context_minimize(Context *context) { strempty(p->new_label), root, fs_uuid, - arg_discard, - /* quiet = */ streq(p->format, "erofs") && !DEBUG_LOGGING, + partition_mkfs_flags(p), context->fs_sector_size, p->compression, p->compression_level, @@ -7941,8 +7951,7 @@ static int context_minimize(Context *context) { strempty(p->new_label), root, p->fs_uuid, - arg_discard, - /* quiet = */ streq(p->format, "erofs") && !DEBUG_LOGGING, + partition_mkfs_flags(p), context->fs_sector_size, p->compression, p->compression_level, diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index 5412a60902..e8412cb4b0 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -322,8 +322,7 @@ int make_filesystem( const char *label, const char *root, sd_id128_t uuid, - bool discard, - bool quiet, + MakeFileSystemFlags flags, uint64_t sector_size, char *compression, char *compression_level, @@ -424,7 +423,7 @@ int make_filesystem( "-U", vol_id, "-I", "256", "-m", "0", - "-E", discard ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1", + "-E", FLAGS_SET(flags, MKFS_DISCARD) ? "discard,lazy_itable_init=1" : "nodiscard,lazy_itable_init=1", "-b", "4096", "-T", "default"); if (!argv) @@ -433,7 +432,7 @@ int make_filesystem( if (root && strv_extend_many(&argv, "-d", root) < 0) return log_oom(); - if (quiet && strv_extend(&argv, "-q") < 0) + if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0) return log_oom(); if (strv_extend(&argv, node) < 0) @@ -454,13 +453,13 @@ int make_filesystem( if (!argv) return log_oom(); - if (!discard && strv_extend(&argv, "--nodiscard") < 0) + if (!FLAGS_SET(flags, MKFS_DISCARD) && strv_extend(&argv, "--nodiscard") < 0) return log_oom(); if (root && strv_extend_many(&argv, "-r", root) < 0) return log_oom(); - if (quiet && strv_extend(&argv, "-q") < 0) + if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0) return log_oom(); if (compression) { @@ -479,7 +478,7 @@ int make_filesystem( /* mkfs.btrfs unconditionally warns about several settings changing from v5.15 onwards which * isn't silenced by "-q", so let's redirect stdout to /dev/null as well. */ - if (quiet) + if (FLAGS_SET(flags, MKFS_QUIET)) stdio_fds[1] = -EBADF; /* mkfs.btrfs expects a sector size of at least 4k bytes. */ @@ -495,11 +494,11 @@ int make_filesystem( "-f", /* force override, without this it doesn't seem to want to write to an empty partition */ "-l", label, "-U", vol_id, - "-t", one_zero(discard)); + "-t", one_zero(FLAGS_SET(flags, MKFS_DISCARD))); if (!argv) return log_oom(); - if (quiet && strv_extend(&argv, "-q") < 0) + if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0) return log_oom(); if (sector_size > 0) { @@ -525,7 +524,7 @@ int make_filesystem( if (!argv) return log_oom(); - if (!discard && strv_extend(&argv, "-K") < 0) + if (!FLAGS_SET(flags, MKFS_DISCARD) && strv_extend(&argv, "-K") < 0) return log_oom(); if (root) { @@ -557,7 +556,7 @@ int make_filesystem( return log_oom(); } - if (quiet && strv_extend(&argv, "-q") < 0) + if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0) return log_oom(); if (strv_extend(&argv, node) < 0) @@ -584,7 +583,7 @@ int make_filesystem( return log_oom(); /* mkfs.vfat does not have a --quiet option so let's redirect stdout to /dev/null instead. */ - if (quiet) + if (FLAGS_SET(flags, MKFS_QUIET)) stdio_fds[1] = -EBADF; } else if (streq(fstype, "swap")) { @@ -597,7 +596,7 @@ int make_filesystem( if (!argv) return log_oom(); - if (quiet) + if (FLAGS_SET(flags, MKFS_QUIET)) stdio_fds[1] = -EBADF; } else if (streq(fstype, "squashfs")) { @@ -617,7 +616,7 @@ int make_filesystem( } /* mksquashfs -quiet option is pretty new so let's redirect stdout to /dev/null instead. */ - if (quiet) + if (FLAGS_SET(flags, MKFS_QUIET)) stdio_fds[1] = -EBADF; } else if (streq(fstype, "erofs")) { @@ -626,7 +625,7 @@ int make_filesystem( if (!argv) return log_oom(); - if (quiet && strv_extend(&argv, "--quiet") < 0) + if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "--quiet") < 0) return log_oom(); if (compression) { diff --git a/src/shared/mkfs-util.h b/src/shared/mkfs-util.h index e20d9dc63e..ca2fae7e3e 100644 --- a/src/shared/mkfs-util.h +++ b/src/shared/mkfs-util.h @@ -5,6 +5,11 @@ #include "forward.h" +typedef enum MakeFilesystemFlags { + MKFS_QUIET = 1 << 0, /* Suppress mkfs command output */ + MKFS_DISCARD = 1 << 1, /* Enable 'discard' mode on the filesystem */ +} MakeFileSystemFlags; + int mkfs_exists(const char *fstype); int mkfs_supports_root_option(const char *fstype); @@ -15,8 +20,7 @@ int make_filesystem( const char *label, const char *root, sd_id128_t uuid, - bool discard, - bool quiet, + MakeFileSystemFlags flags, uint64_t sector_size, char *compression, char *compression_level, diff --git a/src/test/test-loop-block.c b/src/test/test-loop-block.c index d59b4b27b1..b9a1ffd540 100644 --- a/src/test/test-loop-block.c +++ b/src/test/test-loop-block.c @@ -266,16 +266,16 @@ static int run(int argc, char *argv[]) { assert_se(r >= 0); assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, true, false, 0, NULL, NULL, NULL) >= 0); + assert_se(make_filesystem(dissected->partitions[PARTITION_ESP].node, "vfat", "EFI", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, true, false, 0, NULL, NULL, NULL) >= 0); + assert_se(make_filesystem(dissected->partitions[PARTITION_XBOOTLDR].node, "vfat", "xbootldr", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, true, false, 0, NULL, NULL, NULL) >= 0); + assert_se(make_filesystem(dissected->partitions[PARTITION_ROOT].node, "ext4", "root", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); assert_se(sd_id128_randomize(&id) >= 0); - assert_se(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, true, false, 0, NULL, NULL, NULL) >= 0); + assert_se(make_filesystem(dissected->partitions[PARTITION_HOME].node, "ext4", "home", NULL, id, MKFS_DISCARD, 0, NULL, NULL, NULL) >= 0); dissected = dissected_image_unref(dissected); From a46bb377105153c734bab690508c3d3bbc8248d3 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Mon, 2 Dec 2024 21:00:47 +0100 Subject: [PATCH 5/7] mkfs-util: add fsverity option to make_filesystem() Add an fsverity flag to MkfsFlags and use it to pass the `-O verity` option when creating an ext4 or f2fs filesystem: they share the same argument for this. The only other filesystem that currently supports fs-verity is btrfs and it doesn't require a flag to be enabled when creating the filesystem. Nothing uses this yet. Signed-off-by: Allison Karlitskaya --- src/shared/mkfs-util.c | 6 ++++++ src/shared/mkfs-util.h | 1 + 2 files changed, 7 insertions(+) diff --git a/src/shared/mkfs-util.c b/src/shared/mkfs-util.c index e8412cb4b0..629d02207c 100644 --- a/src/shared/mkfs-util.c +++ b/src/shared/mkfs-util.c @@ -435,6 +435,9 @@ int make_filesystem( if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0) return log_oom(); + if (FLAGS_SET(flags, MKFS_FS_VERITY) && strv_extend_many(&argv, "-O", "verity") < 0) + return log_oom(); + if (strv_extend(&argv, node) < 0) return log_oom(); @@ -501,6 +504,9 @@ int make_filesystem( if (FLAGS_SET(flags, MKFS_QUIET) && strv_extend(&argv, "-q") < 0) return log_oom(); + if (FLAGS_SET(flags, MKFS_FS_VERITY) && strv_extend_many(&argv, "-O", "verity") < 0) + return log_oom(); + if (sector_size > 0) { if (strv_extend(&argv, "-w") < 0) return log_oom(); diff --git a/src/shared/mkfs-util.h b/src/shared/mkfs-util.h index ca2fae7e3e..2e83370d03 100644 --- a/src/shared/mkfs-util.h +++ b/src/shared/mkfs-util.h @@ -8,6 +8,7 @@ typedef enum MakeFilesystemFlags { MKFS_QUIET = 1 << 0, /* Suppress mkfs command output */ MKFS_DISCARD = 1 << 1, /* Enable 'discard' mode on the filesystem */ + MKFS_FS_VERITY = 1 << 2, /* Enable fs-verity support on the filesystem */ } MakeFileSystemFlags; int mkfs_exists(const char *fstype); From 80f00d67ad203fed60f4cc119ca2d52496df5f2a Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Mon, 2 Dec 2024 10:46:46 +0100 Subject: [PATCH 6/7] repart: Collect CopyFiles= lines into structs We currently convert the source:target pairs of the `CopyFiles=` lines in `repart.d` files into a pairwise strv. This works great if the only thing that can be specified is a source and a target, but we're about to add a flags field. Let's start by making this a bit more explicit: we now turn each `CopyFiles=` line into a `CopyFilesLine` struct. We keep an array of those in the `Partition` now, instead of the strv. So far this is a whole lot of added complexity for nothing, but it's necessary for the next step. Signed-off-by: Allison Karlitskaya --- src/repart/repart.c | 108 ++++++++++++++++++++++++++++---------------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/src/repart/repart.c b/src/repart/repart.c index d46caf39af..f72876ddad 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -277,6 +277,22 @@ static PartitionEncryptedVolume* partition_encrypted_volume_free(PartitionEncryp return mfree(c); } +typedef struct CopyFiles { + char *source; + char *target; +} CopyFiles; + +static void copy_files_free_many(CopyFiles *f, size_t n) { + assert(f || n == 0); + + FOREACH_ARRAY(i, f, n) { + free(i->source); + free(i->target); + } + + free(f); +} + typedef enum SubvolumeFlags { SUBVOLUME_RO = 1 << 0, _SUBVOLUME_FLAGS_MASK = SUBVOLUME_RO, @@ -377,7 +393,6 @@ typedef struct Partition { uint64_t copy_blocks_done; char *format; - char **copy_files; char **exclude_files_source; char **exclude_files_target; char **make_directories; @@ -394,6 +409,8 @@ typedef struct Partition { char *compression_level; int add_validatefs; + CopyFiles *copy_files; + size_t n_copy_files; uint64_t gpt_flags; int no_auto; @@ -633,7 +650,6 @@ static Partition* partition_free(Partition *p) { safe_close(p->copy_blocks_fd); free(p->format); - strv_free(p->copy_files); strv_free(p->exclude_files_source); strv_free(p->exclude_files_target); strv_free(p->make_directories); @@ -644,11 +660,12 @@ static Partition* partition_free(Partition *p) { free(p->compression); free(p->compression_level); + copy_files_free_many(p->copy_files, p->n_copy_files); + iovec_done(&p->roothash); free(p->split_name_format); unlink_and_free(p->split_path); - partition_mountpoint_free_many(p->mountpoints, p->n_mountpoints); p->mountpoints = NULL; p->n_mountpoints = 0; @@ -674,7 +691,6 @@ static void partition_foreignize(Partition *p) { p->copy_blocks_root = NULL; p->format = mfree(p->format); - p->copy_files = strv_free(p->copy_files); p->exclude_files_source = strv_free(p->exclude_files_source); p->exclude_files_target = strv_free(p->exclude_files_target); p->make_directories = strv_free(p->make_directories); @@ -685,6 +701,10 @@ static void partition_foreignize(Partition *p) { p->compression = mfree(p->compression); p->compression_level = mfree(p->compression_level); + copy_files_free_many(p->copy_files, p->n_copy_files); + p->copy_files = NULL; + p->n_copy_files = 0; + p->priority = 0; p->weight = 1000; p->padding_weight = 0; @@ -1750,8 +1770,8 @@ static int config_parse_copy_files( void *userdata) { _cleanup_free_ char *source = NULL, *buffer = NULL, *resolved_source = NULL, *resolved_target = NULL; + Partition *partition = ASSERT_PTR(data); const char *p = rvalue, *target; - char ***copy_files = ASSERT_PTR(data); int r; assert(rvalue); @@ -1797,10 +1817,14 @@ static int config_parse_copy_files( if (r < 0) return 0; - r = strv_consume_pair(copy_files, TAKE_PTR(resolved_source), TAKE_PTR(resolved_target)); - if (r < 0) + if (!GREEDY_REALLOC(partition->copy_files, partition->n_copy_files + 1)) return log_oom(); + partition->copy_files[partition->n_copy_files++] = (CopyFiles) { + .source = TAKE_PTR(resolved_source), + .target = TAKE_PTR(resolved_target), + }; + return 0; } @@ -2358,7 +2382,7 @@ static bool partition_needs_populate(const Partition *p) { assert(p); assert(!p->supplement_for || !p->suppressing); /* Avoid infinite recursion */ - return !strv_isempty(p->copy_files) || + return p->n_copy_files > 0 || !strv_isempty(p->make_directories) || !strv_isempty(p->make_symlinks) || partition_add_validatefs(p) || @@ -2393,7 +2417,7 @@ static int partition_read_definition(Partition *p, const char *path, const char { "Partition", "FactoryReset", config_parse_bool, 0, &p->factory_reset }, { "Partition", "CopyBlocks", config_parse_copy_blocks, 0, p }, { "Partition", "Format", config_parse_fstype, 0, &p->format }, - { "Partition", "CopyFiles", config_parse_copy_files, 0, &p->copy_files }, + { "Partition", "CopyFiles", config_parse_copy_files, 0, p }, { "Partition", "ExcludeFiles", config_parse_exclude_files, 0, &p->exclude_files_source }, { "Partition", "ExcludeFilesTarget", config_parse_exclude_files, 0, &p->exclude_files_target }, { "Partition", "MakeDirectories", config_parse_make_dirs, 0, &p->make_directories }, @@ -5655,7 +5679,6 @@ static int file_is_denylisted(const char *source, Hashmap *denylist) { static int do_copy_files(Context *context, Partition *p, const char *root) { _cleanup_strv_free_ char **subvolumes = NULL; - _cleanup_free_ char **override_copy_files = NULL; int r; assert(p); @@ -5665,31 +5688,36 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r < 0) return r; + _cleanup_free_ CopyFiles *copy_files = newdup(CopyFiles, p->copy_files, p->n_copy_files); + if (!copy_files) + return log_oom(); + + size_t n_copy_files = p->n_copy_files; if (p->suppressing) { - r = shallow_join_strv(&override_copy_files, p->copy_files, p->suppressing->copy_files); - if (r < 0) - return r; + if (!GREEDY_REALLOC_APPEND(copy_files, n_copy_files, + p->suppressing->copy_files, p->suppressing->n_copy_files)) + return log_oom(); } /* copy_tree_at() automatically copies the permissions of source directories to target directories if * it created them. However, the root directory is created by us, so we have to manually take care * that it is initialized. We use the first source directory targeting "/" as the metadata source for * the root directory. */ - STRV_FOREACH_PAIR(source, target, override_copy_files ?: p->copy_files) { + FOREACH_ARRAY(line, copy_files, n_copy_files) { _cleanup_close_ int rfd = -EBADF, sfd = -EBADF; - if (!path_equal(*target, "/")) + if (!path_equal(line->target, "/")) continue; rfd = open(root, O_DIRECTORY|O_CLOEXEC|O_NOFOLLOW); if (rfd < 0) return -errno; - sfd = chase_and_open(*source, arg_copy_source, CHASE_PREFIX_ROOT, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOCTTY, NULL); + sfd = chase_and_open(line->source, arg_copy_source, CHASE_PREFIX_ROOT, O_PATH|O_DIRECTORY|O_CLOEXEC|O_NOCTTY, NULL); if (sfd == -ENOTDIR) continue; if (sfd < 0) - return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_copy_source), *source); + return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_copy_source), line->source); (void) copy_xattr(sfd, NULL, rfd, NULL, COPY_ALL_XATTRS); (void) copy_access(sfd, rfd); @@ -5698,50 +5726,50 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { break; } - STRV_FOREACH_PAIR(source, target, override_copy_files ?: p->copy_files) { + FOREACH_ARRAY(line, copy_files, n_copy_files) { _cleanup_hashmap_free_ Hashmap *denylist = NULL; _cleanup_set_free_ Set *subvolumes_by_source_inode = NULL; _cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF; usec_t ts = epoch_or_infinity(); - r = make_copy_files_denylist(context, p, *source, *target, &denylist); + r = make_copy_files_denylist(context, p, line->source, line->target, &denylist); if (r < 0) return r; if (r > 0) continue; - r = make_subvolumes_set(p, *source, *target, &subvolumes_by_source_inode); + r = make_subvolumes_set(p, line->source, line->target, &subvolumes_by_source_inode); if (r < 0) return r; - sfd = chase_and_open(*source, arg_copy_source, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL); + sfd = chase_and_open(line->source, arg_copy_source, CHASE_PREFIX_ROOT, O_CLOEXEC|O_NOCTTY, NULL); if (sfd == -ENOENT) { - log_notice_errno(sfd, "Failed to open source file '%s%s', skipping: %m", strempty(arg_copy_source), *source); + log_notice_errno(sfd, "Failed to open source file '%s%s', skipping: %m", strempty(arg_copy_source), line->source); continue; } if (sfd < 0) - return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_copy_source), *source); + return log_error_errno(sfd, "Failed to open source file '%s%s': %m", strempty(arg_copy_source), line->source); r = fd_verify_regular(sfd); if (r < 0) { if (r != -EISDIR) - return log_error_errno(r, "Failed to check type of source file '%s': %m", *source); + return log_error_errno(r, "Failed to check type of source file '%s': %m", line->source); /* We are looking at a directory */ - tfd = chase_and_open(*target, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL); + tfd = chase_and_open(line->target, root, CHASE_PREFIX_ROOT, O_RDONLY|O_DIRECTORY|O_CLOEXEC, NULL); if (tfd < 0) { _cleanup_free_ char *dn = NULL, *fn = NULL; if (tfd != -ENOENT) - return log_error_errno(tfd, "Failed to open target directory '%s': %m", *target); + return log_error_errno(tfd, "Failed to open target directory '%s': %m", line->target); - r = path_extract_filename(*target, &fn); + r = path_extract_filename(line->target, &fn); if (r < 0) - return log_error_errno(r, "Failed to extract filename from '%s': %m", *target); + return log_error_errno(r, "Failed to extract filename from '%s': %m", line->target); - r = path_extract_directory(*target, &dn); + r = path_extract_directory(line->target, &dn); if (r < 0) - return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); + return log_error_errno(r, "Failed to extract directory from '%s': %m", line->target); r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, subvolumes); if (r < 0) @@ -5766,30 +5794,30 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { denylist, subvolumes_by_source_inode); if (r < 0) return log_error_errno(r, "Failed to copy '%s%s' to '%s%s': %m", - strempty(arg_copy_source), *source, strempty(root), *target); + strempty(arg_copy_source), line->source, strempty(root), line->target); } else { _cleanup_free_ char *dn = NULL, *fn = NULL; /* We are looking at a regular file */ - r = file_is_denylisted(*source, denylist); + r = file_is_denylisted(line->source, denylist); if (r < 0) return r; if (r > 0) { - log_debug("%s is in the denylist, ignoring", *source); + log_debug("%s is in the denylist, ignoring", line->source); continue; } - r = path_extract_filename(*target, &fn); + r = path_extract_filename(line->target, &fn); if (r == -EADDRNOTAVAIL || r == O_DIRECTORY) return log_error_errno(SYNTHETIC_ERRNO(EISDIR), - "Target path '%s' refers to a directory, but source path '%s' refers to regular file, can't copy.", *target, *source); + "Target path '%s' refers to a directory, but source path '%s' refers to regular file, can't copy.", line->target, line->source); if (r < 0) - return log_error_errno(r, "Failed to extract filename from '%s': %m", *target); + return log_error_errno(r, "Failed to extract filename from '%s': %m", line->target); - r = path_extract_directory(*target, &dn); + r = path_extract_directory(line->target, &dn); if (r < 0) - return log_error_errno(r, "Failed to extract directory from '%s': %m", *target); + return log_error_errno(r, "Failed to extract directory from '%s': %m", line->target); r = mkdir_p_root_full(root, dn, UID_INVALID, GID_INVALID, 0755, ts, subvolumes); if (r < 0) @@ -5801,11 +5829,11 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { tfd = openat(pfd, fn, O_CREAT|O_EXCL|O_WRONLY|O_CLOEXEC, 0700); if (tfd < 0) - return log_error_errno(errno, "Failed to create target file '%s': %m", *target); + return log_error_errno(errno, "Failed to create target file '%s': %m", line->target); r = copy_bytes(sfd, tfd, UINT64_MAX, COPY_REFLINK|COPY_HOLES|COPY_SIGINT|COPY_TRUNCATE); if (r < 0) - return log_error_errno(r, "Failed to copy '%s' to '%s%s': %m", *source, strempty(arg_copy_source), *target); + return log_error_errno(r, "Failed to copy '%s' to '%s%s': %m", line->source, strempty(arg_copy_source), line->target); (void) copy_xattr(sfd, NULL, tfd, NULL, COPY_ALL_XATTRS); (void) copy_access(sfd, tfd); From eef63efcc3f3b7fdbac2fc8e1708f8ab1b78ad40 Mon Sep 17 00:00:00 2001 From: Allison Karlitskaya Date: Mon, 2 Dec 2024 10:50:02 +0100 Subject: [PATCH 7/7] repart: add 'fsverity' flag for CopyFiles= lines We currently pass the CopyFlags that we use to populate the temporary directory in the form of a constant at each of the copy_tree_at() call sites. De-duplicate that and move it into the `CopyFilesLine` struct, initializing it from the parser. Add our first non-constant flag: `fsverity=`. This can be set to `off` (the default) or `copy`, in which case we copy the fs-verity state from the source files. This arrangement is amenable to the introduction of more flags to `CopyFiles=` lines, if we want to add them in the future. Update the `repart.d(5)` manpage. Closes #35352 Signed-off-by: Allison Karlitskaya --- man/repart.d.xml | 46 +++++++++++++++++++++++++++++++++------------ src/repart/repart.c | 43 +++++++++++++++++++++++++++++++++++++++--- 2 files changed, 74 insertions(+), 15 deletions(-) diff --git a/man/repart.d.xml b/man/repart.d.xml index fde83b0978..71c520d643 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -426,18 +426,40 @@ CopyFiles= - Takes a pair of colon separated absolute file system paths. The first path refers to - a source file or directory on the host, the second path refers to a target in the file system of the - newly created partition and formatted file system. This setting may be used to copy files or - directories from the host into the file system that is created due to the Format= - option. If CopyFiles= is used without Format= specified - explicitly, Format= with a suitable default is implied (currently - vfat for ESP and XBOOTLDR partitions, and - ext4 otherwise, but this may change in the future). This option may be used - multiple times to copy multiple files or directories from host into the newly formatted file system. - The colon and second path may be omitted in which case the source path is also used as the target - path (relative to the root of the newly created file system). If the source path refers to a - directory it is copied recursively. + Takes a colon-separated triplet in the form + source[:target[:options]]. + source is an absolute path which refers to a source file or directory on the host. + target is an absolute path in the file system of the newly created partition and + formatted file system. options is a comma-separated list of options where each + option is in the form key[=value]. + + This setting may be used to copy files or directories from the host into the file system that + is created due to the Format= option. If CopyFiles= is used + without Format= specified explicitly, Format= with a suitable + default is implied (currently vfat for ESP and + XBOOTLDR partitions, and ext4 otherwise, but this may change in + the future). This option may be used multiple times to copy multiple files or directories from host + into the newly formatted file system. + + The target path may be omitted in which case the source + path is also used as the target path (relative to the root of the newly created file system). If + the source path refers to a directory it is copied recursively. + + The options may contain the following values: + + + + fsverity= + May be set to the value off (the default if the option is not + present) or copy. If set to off then no files copied into + the filesystem from this source will have fs-verity enabled. If set to copy + then the fs-verity information for each file will be copied from the corresponding source + file. + + + + + This option has no effect if the partition already exists: it cannot be used to copy additional files into an existing partition, it may only be used to populate a file system created anew. diff --git a/src/repart/repart.c b/src/repart/repart.c index f72876ddad..f3cc69ef11 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -280,6 +280,7 @@ static PartitionEncryptedVolume* partition_encrypted_volume_free(PartitionEncryp typedef struct CopyFiles { char *source; char *target; + CopyFlags flags; } CopyFiles; static void copy_files_free_many(CopyFiles *f, size_t n) { @@ -1769,7 +1770,7 @@ static int config_parse_copy_files( void *data, void *userdata) { - _cleanup_free_ char *source = NULL, *buffer = NULL, *resolved_source = NULL, *resolved_target = NULL; + _cleanup_free_ char *source = NULL, *buffer = NULL, *resolved_source = NULL, *resolved_target = NULL, *options = NULL; Partition *partition = ASSERT_PTR(data); const char *p = rvalue, *target; int r; @@ -1792,9 +1793,38 @@ static int config_parse_copy_files( else target = buffer; + r = extract_first_word(&p, &options, ":", EXTRACT_CUNESCAPE|EXTRACT_DONT_COALESCE_SEPARATORS); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to extract options: %s", rvalue); + if (!isempty(p)) return log_syntax(unit, LOG_ERR, filename, line, SYNTHETIC_ERRNO(EINVAL), "Too many arguments: %s", rvalue); + CopyFlags flags = COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE|COPY_RESTORE_DIRECTORY_TIMESTAMPS; + for (const char *opts = options;;) { + _cleanup_free_ char *word = NULL; + const char *val; + + r = extract_first_word(&opts, &word, ",", EXTRACT_DONT_COALESCE_SEPARATORS | EXTRACT_UNESCAPE_SEPARATORS); + if (r < 0) + return log_syntax(unit, LOG_ERR, filename, line, r, "Failed to parse CopyFile options: %s", options); + if (r == 0) + break; + + if (isempty(word)) + continue; + + if ((val = startswith(word, "fsverity="))) { + if (streq(val, "copy")) + flags |= COPY_PRESERVE_FS_VERITY; + else if (streq(val, "off")) + flags &= ~COPY_PRESERVE_FS_VERITY; + else + log_syntax(unit, LOG_WARNING, filename, line, 0, "fsverity= expects either 'off' or 'copy'."); + } else + log_syntax(unit, LOG_WARNING, filename, line, 0, "Encountered unknown option '%s', ignoring.", word); + } + r = specifier_printf(source, PATH_MAX-1, system_and_tmp_specifier_table, arg_root, NULL, &resolved_source); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, @@ -1823,6 +1853,7 @@ static int config_parse_copy_files( partition->copy_files[partition->n_copy_files++] = (CopyFiles) { .source = TAKE_PTR(resolved_source), .target = TAKE_PTR(resolved_target), + .flags = flags, }; return 0; @@ -2398,6 +2429,12 @@ static MakeFileSystemFlags partition_mkfs_flags(const Partition *p) { if (streq(p->format, "erofs") && !DEBUG_LOGGING) flags |= MKFS_QUIET; + FOREACH_ARRAY(cf, p->copy_files, p->n_copy_files) + if (cf->flags & COPY_PRESERVE_FS_VERITY) { + flags |= MKFS_FS_VERITY; + break; + } + return flags; } @@ -5783,14 +5820,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { sfd, ".", pfd, fn, UID_INVALID, GID_INVALID, - COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE|COPY_RESTORE_DIRECTORY_TIMESTAMPS, + line->flags, denylist, subvolumes_by_source_inode); } else r = copy_tree_at( sfd, ".", tfd, ".", UID_INVALID, GID_INVALID, - COPY_REFLINK|COPY_HOLES|COPY_MERGE|COPY_REPLACE|COPY_SIGINT|COPY_HARDLINKS|COPY_ALL_XATTRS|COPY_GRACEFUL_WARN|COPY_TRUNCATE|COPY_RESTORE_DIRECTORY_TIMESTAMPS, + line->flags, denylist, subvolumes_by_source_inode); if (r < 0) return log_error_errno(r, "Failed to copy '%s%s' to '%s%s': %m",