From: Alberto Planas Date: Wed, 4 Jun 2025 17:56:18 +0000 (+0200) Subject: repart: support "nodatacow" in btrfs subvolumes X-Git-Tag: v259-rc1~187 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=ab1f4e506f3ea99e33563086c6477ded8e3f3f97;p=thirdparty%2Fsystemd.git repart: support "nodatacow" in btrfs subvolumes In btrfs-progs 6.15 it is planned to add a new parameter in mkfs.btrfs --inode-flags, that can set attributes for subvolumes, directories, and files. The current supported attributes are "nodatacow", to disable CoW, and "nodatasum", to disable the checksum. This commit extend the "Subvolunes=" option to understand the "nodatacow" flag for subvolums only. If RepartOffline is enabled it will build the image without loopback devices, using the correct --inode-flags parameters. If RepartOffline is disabled it will use loopback devices and set the btrfs attributes accordingly. Signed-off-by: Alberto Planas --- diff --git a/man/repart.d.xml b/man/repart.d.xml index 6191d35b76e..486fea96b90 100644 --- a/man/repart.d.xml +++ b/man/repart.d.xml @@ -612,6 +612,10 @@ ro Make this subvolume read-only. + + nodatacow + Disable data CoW for this subvolume. + diff --git a/src/basic/btrfs.h b/src/basic/btrfs.h index 14932704891..4023a2fc0ea 100644 --- a/src/basic/btrfs.h +++ b/src/basic/btrfs.h @@ -1,7 +1,16 @@ /* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once #include "basic-forward.h" +typedef enum BtrfsSubvolFlags { + BTRFS_SUBVOL_RO = 1 << 0, + BTRFS_SUBVOL_NODATACOW = 1 << 1, + _BTRFS_SUBVOL_FLAGS_MASK = BTRFS_SUBVOL_NODATACOW|BTRFS_SUBVOL_RO, + _BTRFS_SUBVOL_FLAGS_INVALID = -EINVAL, + _BTRFS_SUBVOL_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */ +} BtrfsSubvolFlags; + int btrfs_validate_subvolume_name(const char *name); int btrfs_subvol_make(int dir_fd, const char *path); diff --git a/src/basic/mkdir.c b/src/basic/mkdir.c index 384b8606114..5f1ecad5625 100644 --- a/src/basic/mkdir.c +++ b/src/basic/mkdir.c @@ -9,6 +9,7 @@ #include "fd-util.h" #include "format-util.h" #include "fs-util.h" +#include "hashmap.h" #include "log.h" #include "mkdir.h" #include "path-util.h" @@ -202,7 +203,7 @@ int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, g return mkdir_p_internal(prefix, path, mode, uid, gid, flags, mkdirat_errno_wrapper); } -int mkdir_p_root_full(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, usec_t ts, char **subvolumes) { +int mkdir_p_root_full(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, usec_t ts, Hashmap *subvolumes) { _cleanup_free_ char *pp = NULL, *bn = NULL; _cleanup_close_ int dfd = -EBADF; int r; @@ -237,10 +238,17 @@ int mkdir_p_root_full(const char *root, const char *p, uid_t uid, gid_t gid, mod if (r < 0) return r; + XOpenFlags flags = 0; + if (hashmap_contains(subvolumes, p)) { + flags = XO_SUBVOLUME; + if ((PTR_TO_INT(hashmap_get(subvolumes, p)) & BTRFS_SUBVOL_NODATACOW)) + flags |= XO_NOCOW; + } + _cleanup_close_ int nfd = xopenat_full( dfd, bn, O_DIRECTORY|O_CREAT|O_EXCL|O_NOFOLLOW|O_CLOEXEC, - path_strv_contains(subvolumes, p) ? XO_SUBVOLUME : 0, + flags, m); if (nfd == -EEXIST) return 0; diff --git a/src/basic/mkdir.h b/src/basic/mkdir.h index 82323b1058b..f286598a2a8 100644 --- a/src/basic/mkdir.h +++ b/src/basic/mkdir.h @@ -22,7 +22,7 @@ static inline int mkdir_parents(const char *path, mode_t mode) { int mkdir_parents_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags); int mkdir_p(const char *path, mode_t mode); int mkdir_p_safe(const char *prefix, const char *path, mode_t mode, uid_t uid, gid_t gid, MkdirFlags flags); -int mkdir_p_root_full(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, usec_t ts, char **subvolumes); +int mkdir_p_root_full(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m, usec_t ts, Hashmap *subvolumes); static inline int mkdir_p_root(const char *root, const char *p, uid_t uid, gid_t gid, mode_t m) { return mkdir_p_root_full(root, p, uid, gid, m, USEC_INFINITY, NULL); } diff --git a/src/repart/repart.c b/src/repart/repart.c index a04fa1bf6a6..30395e1ca1b 100644 --- a/src/repart/repart.c +++ b/src/repart/repart.c @@ -332,33 +332,29 @@ static void copy_files_free_many(CopyFiles *f, size_t n) { free(f); } -typedef enum SubvolumeFlags { - SUBVOLUME_RO = 1 << 0, - _SUBVOLUME_FLAGS_MASK = SUBVOLUME_RO, - _SUBVOLUME_FLAGS_INVALID = -EINVAL, - _SUBVOLUME_FLAGS_ERRNO_MAX = -ERRNO_MAX, /* Ensure the whole errno range fits into this enum */ -} SubvolumeFlags; - -static SubvolumeFlags subvolume_flags_from_string_one(const char *s) { +static BtrfsSubvolFlags subvolume_flags_from_string_one(const char *s) { /* This is a bitmask (i.e. not dense), hence we don't use the "string-table.h" stuff here. */ assert(s); if (streq(s, "ro")) - return SUBVOLUME_RO; + return BTRFS_SUBVOL_RO; - return _SUBVOLUME_FLAGS_INVALID; + if (streq(s, "nodatacow")) + return BTRFS_SUBVOL_NODATACOW; + + return _BTRFS_SUBVOL_FLAGS_INVALID; } -static SubvolumeFlags subvolume_flags_from_string(const char *s) { - SubvolumeFlags flags = 0; +static BtrfsSubvolFlags subvolume_flags_from_string(const char *s) { + BtrfsSubvolFlags flags = 0; int r; assert(s); for (;;) { _cleanup_free_ char *f = NULL; - SubvolumeFlags ff; + BtrfsSubvolFlags ff; r = extract_first_word(&s, &f, ",", EXTRACT_DONT_COALESCE_SEPARATORS); if (r < 0) @@ -378,7 +374,7 @@ static SubvolumeFlags subvolume_flags_from_string(const char *s) { typedef struct Subvolume { char *path; - SubvolumeFlags flags; + BtrfsSubvolFlags flags; } Subvolume; static Subvolume* subvolume_free(Subvolume *s) { @@ -2327,7 +2323,7 @@ static int config_parse_subvolumes( } if (f) { - SubvolumeFlags flags = subvolume_flags_from_string(f); + BtrfsSubvolFlags flags = subvolume_flags_from_string(f); if (flags == -EBADRQC) { log_syntax(unit, LOG_WARNING, filename, line, r, "Unknown subvolume flag in subvolume, ignoring: %s", f); continue; @@ -5973,7 +5969,7 @@ static int make_copy_files_denylist( return 0; } -static int add_subvolume_path(const char *path, Set **subvolumes) { +static int add_subvolume_path(const char *path, BtrfsSubvolFlags flags, Hashmap **subvolumes) { _cleanup_free_ struct stat *st = NULL; int r; @@ -5990,63 +5986,71 @@ static int add_subvolume_path(const char *path, Set **subvolumes) { if (r < 0) return log_error_errno(r, "Failed to stat source file '%s/%s': %m", strempty(arg_copy_source), path); - r = set_ensure_consume(subvolumes, &inode_hash_ops, TAKE_PTR(st)); + r = hashmap_ensure_put(subvolumes, &inode_hash_ops, st, INT_TO_PTR(flags)); if (r < 0) return log_oom(); + TAKE_PTR(st); + return 0; } -static int make_subvolumes_strv(const Partition *p, char ***ret) { - _cleanup_strv_free_ char **subvolumes = NULL; +static int make_subvolumes_hashmap(const Partition *p, Hashmap **ret) { + _cleanup_hashmap_free_ Hashmap *hashmap = NULL; Subvolume *subvolume; int r; assert(p); assert(ret); - ORDERED_HASHMAP_FOREACH(subvolume, p->subvolumes) - if (strv_extend(&subvolumes, subvolume->path) < 0) + ORDERED_HASHMAP_FOREACH(subvolume, p->subvolumes) { + _cleanup_free_ char *path = NULL; + + path = strdup(subvolume->path); + if (!path) return log_oom(); + r = hashmap_ensure_put(&hashmap, &path_hash_ops_free, path, INT_TO_PTR(subvolume->flags)); + if (r < 0) + return log_oom(); + + TAKE_PTR(path); + } + if (p->suppressing) { - char **suppressing; + Hashmap *suppressing; - r = make_subvolumes_strv(p->suppressing, &suppressing); + r = make_subvolumes_hashmap(p->suppressing, &suppressing); if (r < 0) return r; - r = strv_extend_strv_consume(&subvolumes, suppressing, /* filter_duplicates= */ true); + r = hashmap_merge(hashmap, suppressing); if (r < 0) return log_oom(); } - *ret = TAKE_PTR(subvolumes); + *ret = TAKE_PTR(hashmap); return 0; } -static int make_subvolumes_set( +static int make_subvolumes_by_source_inode_hashmap( const Partition *p, const char *source, const char *target, - Set **ret) { + Hashmap **ret) { - _cleanup_strv_free_ char **paths = NULL; - _cleanup_set_free_ Set *subvolumes = NULL; + _cleanup_hashmap_free_ Hashmap *hashmap = NULL; + Subvolume *subvolume; int r; assert(p); assert(target); assert(ret); - r = make_subvolumes_strv(p, &paths); - if (r < 0) - return r; - - STRV_FOREACH(subvolume, paths) { + ORDERED_HASHMAP_FOREACH(subvolume, p->subvolumes) { _cleanup_free_ char *path = NULL; - const char *s = path_startswith(*subvolume, target); + const char *s = path_startswith(subvolume->path, target); if (!s) continue; @@ -6054,12 +6058,24 @@ static int make_subvolumes_set( if (!path) return log_oom(); - r = add_subvolume_path(path, &subvolumes); + r = add_subvolume_path(path, subvolume->flags, &hashmap); + if (r < 0) + return r; + } + + if (p->suppressing) { + Hashmap *suppressing; + + r = make_subvolumes_by_source_inode_hashmap(p->suppressing, source, target, &suppressing); if (r < 0) return r; + + r = hashmap_merge(hashmap, suppressing); + if (r < 0) + return log_oom(); } - *ret = TAKE_PTR(subvolumes); + *ret = TAKE_PTR(hashmap); return 0; } @@ -6127,13 +6143,13 @@ 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_hashmap_free_ Hashmap *subvolumes = NULL; int r; assert(p); assert(root); - r = make_subvolumes_strv(p, &subvolumes); + r = make_subvolumes_hashmap(p, &subvolumes); if (r < 0) return r; @@ -6177,7 +6193,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { FOREACH_ARRAY(line, copy_files, n_copy_files) { _cleanup_hashmap_free_ Hashmap *denylist = NULL; - _cleanup_set_free_ Set *subvolumes_by_source_inode = NULL; + _cleanup_hashmap_free_ Hashmap *subvolumes_by_source_inode = NULL; _cleanup_close_ int sfd = -EBADF, pfd = -EBADF, tfd = -EBADF; usec_t ts = epoch_or_infinity(); @@ -6187,7 +6203,7 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { if (r > 0) continue; - r = make_subvolumes_set(p, line->source, line->target, &subvolumes_by_source_inode); + r = make_subvolumes_by_source_inode_hashmap(p, line->source, line->target, &subvolumes_by_source_inode); if (r < 0) return r; @@ -6302,14 +6318,14 @@ static int do_copy_files(Context *context, Partition *p, const char *root) { } static int do_make_directories(Partition *p, const char *root) { - _cleanup_strv_free_ char **subvolumes = NULL; + _cleanup_hashmap_free_ Hashmap *subvolumes = NULL; _cleanup_free_ char **override_dirs = NULL; int r; assert(p); assert(root); - r = make_subvolumes_strv(p, &subvolumes); + r = make_subvolumes_hashmap(p, &subvolumes); if (r < 0) return r; @@ -6355,7 +6371,7 @@ static int make_subvolumes_read_only(Partition *p, const char *root) { int r; ORDERED_HASHMAP_FOREACH(subvolume, p->subvolumes) { - if (!FLAGS_SET(subvolume->flags, SUBVOLUME_RO)) + if (!FLAGS_SET(subvolume->flags, BTRFS_SUBVOL_RO)) continue; path = path_join(root, subvolume->path); @@ -6623,7 +6639,7 @@ static int append_btrfs_subvols(char ***l, OrderedHashmap *subvolumes, const cha if (streq_ptr(subvolume->path, default_subvolume) && !strextend(&s, "default")) return log_oom(); - if (FLAGS_SET(subvolume->flags, SUBVOLUME_RO) && !strextend_with_separator(&s, "-", "ro")) + if (FLAGS_SET(subvolume->flags, BTRFS_SUBVOL_RO) && !strextend_with_separator(&s, "-", "ro")) return log_oom(); if (!strextend_with_separator(&s, ":", subvolume->path)) @@ -6637,6 +6653,28 @@ static int append_btrfs_subvols(char ***l, OrderedHashmap *subvolumes, const cha return 0; } +static int append_btrfs_inode_flags(char ***l, OrderedHashmap *subvolumes) { + Subvolume *subvolume; + int r; + + assert(l); + + ORDERED_HASHMAP_FOREACH(subvolume, subvolumes) { + if (!FLAGS_SET(subvolume->flags, BTRFS_SUBVOL_NODATACOW)) + continue; + + _cleanup_free_ char *s = strjoin("nodatacow:", subvolume->path); + if (!s) + return log_oom(); + + r = strv_extend_many(l, "--inode-flags", s); + if (r < 0) + return log_oom(); + } + + return 0; +} + static int finalize_extra_mkfs_options(const Partition *p, const char *root, char ***ret) { _cleanup_strv_free_ char **sv = NULL; int r; @@ -6655,10 +6693,18 @@ static int finalize_extra_mkfs_options(const Partition *p, const char *root, cha if (r < 0) return r; + r = append_btrfs_inode_flags(&sv, p->subvolumes); + if (r < 0) + return r; + if (p->suppressing) { r = append_btrfs_subvols(&sv, p->suppressing->subvolumes, NULL); if (r < 0) return r; + + r = append_btrfs_inode_flags(&sv, p->suppressing->subvolumes); + if (r < 0) + return r; } } diff --git a/src/shared/copy.c b/src/shared/copy.c index 054a01e86b9..7748d2cc533 100644 --- a/src/shared/copy.c +++ b/src/shared/copy.c @@ -12,6 +12,7 @@ #include #include "alloc-util.h" +#include "btrfs.h" #include "chattr-util.h" #include "copy.h" #include "dirent-util.h" @@ -891,7 +892,7 @@ static int fd_copy_tree_generic( gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, - Set *subvolumes, + Hashmap *subvolumes, HardlinkContext *hardlink_context, const char *display_path, copy_progress_path_t progress_path, @@ -1111,7 +1112,7 @@ static int fd_copy_directory( gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, - Set *subvolumes, + Hashmap *subvolumes, HardlinkContext *hardlink_context, const char *display_path, copy_progress_path_t progress_path, @@ -1160,9 +1161,16 @@ static int fd_copy_directory( exists = r >= 0; + XOpenFlags flags = copy_flags & COPY_MAC_CREATE ? XO_LABEL : 0; + if (hashmap_contains(subvolumes, st)) { + flags |= XO_SUBVOLUME; + if ((PTR_TO_INT(hashmap_get(subvolumes, st)) & BTRFS_SUBVOL_NODATACOW)) + flags |= XO_NOCOW; + } + fdt = xopenat_lock_full(dt, to, O_RDONLY|O_DIRECTORY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW|(exists ? 0 : O_CREAT|O_EXCL), - (copy_flags & COPY_MAC_CREATE ? XO_LABEL : 0)|(set_contains(subvolumes, st) ? XO_SUBVOLUME : 0), + flags, st->st_mode & 07777, copy_flags & COPY_LOCK_BSD ? LOCK_BSD : LOCK_NONE, LOCK_EX); @@ -1331,7 +1339,7 @@ static int fd_copy_tree_generic( gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, - Set *subvolumes, + Hashmap *subvolumes, HardlinkContext *hardlink_context, const char *display_path, copy_progress_path_t progress_path, @@ -1380,7 +1388,7 @@ int copy_tree_at_full( gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, - Set *subvolumes, + Hashmap *subvolumes, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata) { diff --git a/src/shared/copy.h b/src/shared/copy.h index 8dd3176e8e4..118fb08a3a0 100644 --- a/src/shared/copy.h +++ b/src/shared/copy.h @@ -80,11 +80,11 @@ static inline int copy_file_atomic(const char *from, const char *to, mode_t mode return copy_file_atomic_full(from, to, mode, 0, 0, copy_flags, NULL, NULL); } -int copy_tree_at_full(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, Set *subvolumes, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); -static inline int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, Set *subvolumes) { +int copy_tree_at_full(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, Hashmap *subvolumes, copy_progress_path_t progress_path, copy_progress_bytes_t progress_bytes, void *userdata); +static inline int copy_tree_at(int fdf, const char *from, int fdt, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, Hashmap *subvolumes) { return copy_tree_at_full(fdf, from, fdt, to, override_uid, override_gid, copy_flags, denylist, subvolumes, NULL, NULL, NULL); } -static inline int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, Set *subvolumes) { +static inline int copy_tree(const char *from, const char *to, uid_t override_uid, gid_t override_gid, CopyFlags copy_flags, Hashmap *denylist, Hashmap *subvolumes) { return copy_tree_at_full(AT_FDCWD, from, AT_FDCWD, to, override_uid, override_gid, copy_flags, denylist, subvolumes, NULL, NULL, NULL); }