diff options
36 files changed, 1318 insertions, 200 deletions
@@ -26,6 +26,8 @@ External: Features: +* do something about "/control" subcgroups in the unified cgroup hierarchy + * when we detect that there are waiting jobs but no running jobs, do something * push CPUAffinity also into the "cpuset" cgroup controller @@ -136,9 +138,6 @@ Features: * .timer units should optionally support CLOCK_BOOTTIME in addition to CLOCK_MONOTONIC -* create a btrfs qgroup for /var/lib/machines, and add all container - subvolumes we create to it. - * When logging about multiple units (stopping BoundTo units, conflicts, etc.), log both units as UNIT=, so that journalctl -u triggers on both. diff --git a/man/systemd-nspawn.xml b/man/systemd-nspawn.xml index 4b0e72113e..ddad762653 100644 --- a/man/systemd-nspawn.xml +++ b/man/systemd-nspawn.xml @@ -850,7 +850,7 @@ <example> <title>Build and boot a minimal Fedora distribution in a container</title> - <programlisting># dnf -y --releasever=21 --nogpg --installroot=/srv/mycontainer --disablerepo='*' --enablerepo=fedora install systemd passwd dnf fedora-release vim-minimal + <programlisting># dnf -y --releasever=23 --installroot=/srv/mycontainer --disablerepo=* --enablerepo=fedora --enablerepo=updates install systemd passwd dnf fedora-release vim-minimal # systemd-nspawn -bD /srv/mycontainer</programlisting> <para>This installs a minimal Fedora distribution into the diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index 3cee0fff59..662ec4518b 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -1,5 +1,4 @@ -<?xml version="1.0"?> -<!--*-nxml-*--> +<?xml version='1.0'?> <!--*- Mode: nxml; nxml-child-indent: 2; indent-tabs-mode: nil -*--> <!DOCTYPE refentry PUBLIC "-//OASIS//DTD DocBook XML V4.2//EN" "http://www.oasis-open.org/docbook/xml/4.2/docbookx.dtd"> <!-- This file is part of systemd. @@ -172,7 +171,77 @@ <listitem><para>Create a subvolume if the path does not exist yet and the file system supports this (btrfs). Otherwise create a normal directory, in the same - way as <varname>d</varname>.</para></listitem> + way as <varname>d</varname>. A subvolume created with this + line type is not assigned to any higher-level quota + group. For that use <varname>q</varname> or + <varname>Q</varname> which allow creating simple quota group + hierarchies, see below.</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>q</varname></term> + <listitem><para>Similar to <varname>v</varname>, however + makes sure that the subvolume will be assigned to the same + higher-level quota groups as the subvolume it has been + created in. This ensures that higher-level limits and + accounting applied to the parent subvolume also include the + specified subvolume. On non-btrfs file systems, this line + type is identical to <varname>d</varname>. If the subvolume + already exists and is already assigned to one or more higher + level quota groups no change to the quota hierarchy is + made. Also see <varname>Q</varname> below. See <citerefentry + project='die-net'><refentrytitle>btrfs-qgroup</refentrytitle><manvolnum>8</manvolnum></citerefentry> + for details about the btrfs quota group + concept.</para></listitem> + </varlistentry> + + <varlistentry> + <term><varname>Q</varname></term> + <listitem><para>Similar to <varname>q</varname>, however + instead of copying the higher-level quota group assignments + from the parent as-is, the lowest quota group of the parent + subvolume is determined that is not the leaf quota + group. Then, an "intermediary" quota group is inserted that + is one level below this level, and shares the same ID part + as the specified subvolume. If no higher-level quota group + exists for the parent subvolume, a new quota group at level + 255 sharing the same ID as the specified subvolume is + inserted instead. This new intermediary quota group is then + assigned to the parent subvolume's higher-level quota + groups, and the specified subvolume's leaf quota group is + assigned to it.</para> + + <para>Effectively, this has a similar effect as + <varname>q</varname>, however introduces a new higher-level + quota group for the specified subvolume that may be used to + enforce limits and accounting to the specified subvolume and + children subvolume created within it. Thus, by creating + subvolumes only via <varname>q</varname> and + <varname>Q</varname> a concept of "subtree quotas" is + implemented. Each subvolume for which <varname>Q</varname> + is set will get a "subtree" quota group created, and all + child subvolumes created within it will be assigned to + it. Each subvolume for which <varname>q</varname> is set + will not get such a "subtree" quota group, but it is ensured + that they are added to the same "subtree" quota group as their + immediate parents.</para> + + <para>It is recommended to use + <varname>Q</varname> for subvolumes that typically contain + further subvolumes, and where it is desirable to have + accounting and quota limits on all child subvolumes + together. Examples for <varname>Q</varname> are typically + <filename>/home</filename> or + <filename>/var/lib/machines</filename>. In contrast, + <varname>q</varname> should be used for subvolumes that + either usually do not include further subvolumes or where no + accounting and quota limits are needed that apply to all + child subvolumes together. Examples for <varname>q</varname> + are typically <filename>/var</filename> or + <filename>/var/tmp</filename>. As with <varname>Q</varname>, + <varname>q</varname> has no effect on the quota group + hierarchy if the subvolume exists and already has at least + one higher-level quota group assigned.</para></listitem> </varlistentry> <varlistentry> @@ -504,13 +573,12 @@ <para>When the age is set to zero, the files are cleaned unconditionally.</para> - <para>The age field only applies to lines - starting with <varname>d</varname>, - <varname>D</varname>, <varname>v</varname>, - <varname>C</varname>, <varname>x</varname> and - <varname>X</varname>. If omitted or set to - <literal>-</literal>, no automatic clean-up is - done.</para> + <para>The age field only applies to lines starting with + <varname>d</varname>, <varname>D</varname>, + <varname>v</varname>, <varname>q</varname>, + <varname>Q</varname>, <varname>C</varname>, <varname>x</varname> + and <varname>X</varname>. If omitted or set to + <literal>-</literal>, no automatic clean-up is done.</para> <para>If the age field starts with a tilde character <literal>~</literal>, the clean-up is only applied to files and @@ -572,7 +640,9 @@ x /var/tmp/abrt/*</programlisting> <citerefentry project='man-pages'><refentrytitle>setfattr</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>setfacl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, <citerefentry project='man-pages'><refentrytitle>getfacl</refentrytitle><manvolnum>1</manvolnum></citerefentry>, - <citerefentry project='man-pages'><refentrytitle>chattr</refentrytitle><manvolnum>1</manvolnum></citerefentry> + <citerefentry project='man-pages'><refentrytitle>chattr</refentrytitle><manvolnum>1</manvolnum></citerefentry>, + <citerefentry project='die-net'><refentrytitle>btrfs-subvolume</refentrytitle><manvolnum>8</manvolnum></citerefentry>, + <citerefentry project='die-net'><refentrytitle>btrfs-qgroup</refentrytitle><manvolnum>8</manvolnum></citerefentry> </para> </refsect1> diff --git a/src/basic/btrfs-util.c b/src/basic/btrfs-util.c index ec7e00986b..f327c16a80 100644 --- a/src/basic/btrfs-util.c +++ b/src/basic/btrfs-util.c @@ -436,7 +436,7 @@ static int btrfs_ioctl_search_args_compare(const struct btrfs_ioctl_search_args #define BTRFS_IOCTL_SEARCH_HEADER_BODY(sh) \ ((void*) ((uint8_t*) sh + sizeof(struct btrfs_ioctl_search_header))) -int btrfs_subvol_get_info_fd(int fd, BtrfsSubvolInfo *ret) { +int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *ret) { struct btrfs_ioctl_search_args args = { /* Tree of tree roots */ .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, @@ -453,16 +453,23 @@ int btrfs_subvol_get_info_fd(int fd, BtrfsSubvolInfo *ret) { .key.max_transid = (uint64_t) -1, }; - uint64_t subvol_id; bool found = false; int r; assert(fd >= 0); assert(ret); - r = btrfs_subvol_get_id_fd(fd, &subvol_id); - if (r < 0) - return r; + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } args.key.min_objectid = args.key.max_objectid = subvol_id; @@ -521,7 +528,7 @@ finish: return 0; } -int btrfs_subvol_get_quota_fd(int fd, BtrfsQuotaInfo *ret) { +int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *ret) { struct btrfs_ioctl_search_args args = { /* Tree of quota items */ @@ -540,18 +547,25 @@ int btrfs_subvol_get_quota_fd(int fd, BtrfsQuotaInfo *ret) { .key.max_transid = (uint64_t) -1, }; - uint64_t subvol_id; bool found_info = false, found_limit = false; int r; assert(fd >= 0); assert(ret); - r = btrfs_subvol_get_id_fd(fd, &subvol_id); - if (r < 0) - return r; + if (qgroupid == 0) { + r = btrfs_subvol_get_id_fd(fd, &qgroupid); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } - args.key.min_offset = args.key.max_offset = subvol_id; + args.key.min_offset = args.key.max_offset = qgroupid; while (btrfs_ioctl_search_args_compare(&args) <= 0) { const struct btrfs_ioctl_search_header *sh; @@ -571,7 +585,7 @@ int btrfs_subvol_get_quota_fd(int fd, BtrfsQuotaInfo *ret) { if (sh->objectid != 0) continue; - if (sh->offset != subvol_id) + if (sh->offset != qgroupid) continue; if (sh->type == BTRFS_QGROUP_INFO_KEY) { @@ -585,12 +599,14 @@ int btrfs_subvol_get_quota_fd(int fd, BtrfsQuotaInfo *ret) { } else if (sh->type == BTRFS_QGROUP_LIMIT_KEY) { const struct btrfs_qgroup_limit_item *qli = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); - ret->referenced_max = le64toh(qli->max_rfer); - ret->exclusive_max = le64toh(qli->max_excl); - - if (ret->referenced_max == 0) + if (le64toh(qli->flags) & BTRFS_QGROUP_LIMIT_MAX_RFER) + ret->referenced_max = le64toh(qli->max_rfer); + else ret->referenced_max = (uint64_t) -1; - if (ret->exclusive_max == 0) + + if (le64toh(qli->flags) & BTRFS_QGROUP_LIMIT_MAX_EXCL) + ret->exclusive_max = le64toh(qli->max_excl); + else ret->exclusive_max = (uint64_t) -1; found_limit = true; @@ -622,6 +638,109 @@ finish: return 0; } +int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *ret) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_qgroup_get_quota_fd(fd, qgroupid, ret); +} + +int btrfs_subvol_find_subtree_qgroup(int fd, uint64_t subvol_id, uint64_t *ret) { + uint64_t level, lowest = (uint64_t) -1, lowest_qgroupid = 0; + _cleanup_free_ uint64_t *qgroups = NULL; + int r, n, i; + + assert(fd >= 0); + assert(ret); + + /* This finds the "subtree" qgroup for a specific + * subvolume. This only works for subvolumes that have been + * prepared with btrfs_subvol_auto_qgroup_fd() with + * insert_intermediary_qgroup=true (or equivalent). For others + * it will return the leaf qgroup instead. The two cases may + * be distuingished via the return value, which is 1 in case + * an appropriate "subtree" qgroup was found, and 0 + * otherwise. */ + + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } + + r = btrfs_qgroupid_split(subvol_id, &level, NULL); + if (r < 0) + return r; + if (level != 0) /* Input must be a leaf qgroup */ + return -EINVAL; + + n = btrfs_qgroup_find_parents(fd, subvol_id, &qgroups); + if (n < 0) + return n; + + for (i = 0; i < n; i++) { + uint64_t id; + + r = btrfs_qgroupid_split(qgroups[i], &level, &id); + if (r < 0) + return r; + + if (id != subvol_id) + continue; + + if (lowest == (uint64_t) -1 || level < lowest) { + lowest_qgroupid = qgroups[i]; + lowest = level; + } + } + + if (lowest == (uint64_t) -1) { + /* No suitable higher-level qgroup found, let's return + * the leaf qgroup instead, and indicate that with the + * return value. */ + + *ret = subvol_id; + return 0; + } + + *ret = lowest_qgroupid; + return 1; +} + +int btrfs_subvol_get_subtree_quota_fd(int fd, uint64_t subvol_id, BtrfsQuotaInfo *ret) { + uint64_t qgroupid; + int r; + + assert(fd >= 0); + assert(ret); + + /* This determines the quota data of the qgroup with the + * lowest level, that shares the id part with the specified + * subvolume. This is useful for determining the quota data + * for entire subvolume subtrees, as long as the subtrees have + * been set up with btrfs_qgroup_subvol_auto_fd() or in a + * compatible way */ + + r = btrfs_subvol_find_subtree_qgroup(fd, subvol_id, &qgroupid); + if (r < 0) + return r; + + return btrfs_qgroup_get_quota_fd(fd, qgroupid, ret); +} + +int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQuotaInfo *ret) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_subvol_get_subtree_quota_fd(fd, subvol_id, ret); +} + int btrfs_defrag_fd(int fd) { struct stat st; @@ -679,37 +798,79 @@ int btrfs_quota_enable(const char *path, bool b) { return btrfs_quota_enable_fd(fd, b); } -int btrfs_quota_limit_fd(int fd, uint64_t referenced_max) { +int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max) { + struct btrfs_ioctl_qgroup_limit_args args = { - .lim.max_rfer = - referenced_max == (uint64_t) -1 ? 0 : - referenced_max == 0 ? 1 : referenced_max, + .lim.max_rfer = referenced_max, .lim.flags = BTRFS_QGROUP_LIMIT_MAX_RFER, }; + unsigned c; int r; assert(fd >= 0); - r = btrfs_is_filesystem(fd); - if (r < 0) - return r; - if (!r) - return -ENOTTY; + if (qgroupid == 0) { + r = btrfs_subvol_get_id_fd(fd, &qgroupid); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } - if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args) < 0) - return -errno; + args.qgroupid = qgroupid; + + for (c = 0;; c++) { + if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &args) < 0) { + + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + + return -errno; + } + + break; + } return 0; } -int btrfs_quota_limit(const char *path, uint64_t referenced_max) { +int btrfs_qgroup_set_limit(const char *path, uint64_t qgroupid, uint64_t referenced_max) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); + if (fd < 0) + return -errno; + + return btrfs_qgroup_set_limit_fd(fd, qgroupid, referenced_max); +} + +int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t referenced_max) { + uint64_t qgroupid; + int r; + + assert(fd >= 0); + + r = btrfs_subvol_find_subtree_qgroup(fd, subvol_id, &qgroupid); + if (r < 0) + return r; + + return btrfs_qgroup_set_limit_fd(fd, qgroupid, referenced_max); +} + +int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, uint64_t referenced_max) { _cleanup_close_ int fd = -1; fd = open(path, O_RDONLY|O_CLOEXEC|O_NOCTTY|O_NOFOLLOW); if (fd < 0) return -errno; - return btrfs_quota_limit_fd(fd, referenced_max); + return btrfs_subvol_set_subtree_quota_limit_fd(fd, subvol_id, referenced_max); } int btrfs_resize_loopback_fd(int fd, uint64_t new_size, bool grow_only) { @@ -799,7 +960,7 @@ int btrfs_resize_loopback(const char *p, uint64_t new_size, bool grow_only) { return btrfs_resize_loopback_fd(fd, new_size, grow_only); } -static int make_qgroup_id(uint64_t level, uint64_t id, uint64_t *ret) { +int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret) { assert(ret); if (level >= (UINT64_C(1) << (64 - BTRFS_QGROUP_LEVEL_SHIFT))) @@ -812,33 +973,175 @@ static int make_qgroup_id(uint64_t level, uint64_t id, uint64_t *ret) { return 0; } -static int qgroup_create_or_destroy(int fd, bool b, uint64_t level, uint64_t id) { +int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id) { + assert(level || id); + + if (level) + *level = qgroupid >> BTRFS_QGROUP_LEVEL_SHIFT; + + if (id) + *id = qgroupid & ((UINT64_C(1) << BTRFS_QGROUP_LEVEL_SHIFT) - 1); + + return 0; +} + +static int qgroup_create_or_destroy(int fd, bool b, uint64_t qgroupid) { struct btrfs_ioctl_qgroup_create_args args = { .create = b, + .qgroupid = qgroupid, }; - + unsigned c; int r; - r = make_qgroup_id(level, id, (uint64_t*) &args.qgroupid); + r = btrfs_is_filesystem(fd); if (r < 0) return r; + if (r == 0) + return -ENOTTY; + + for (c = 0;; c++) { + if (ioctl(fd, BTRFS_IOC_QGROUP_CREATE, &args) < 0) { + + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + + return -errno; + } - if (ioctl(fd, BTRFS_IOC_QGROUP_CREATE, &args) < 0) + break; + } + + return 0; +} + +int btrfs_qgroup_create(int fd, uint64_t qgroupid) { + return qgroup_create_or_destroy(fd, true, qgroupid); +} + +int btrfs_qgroup_destroy(int fd, uint64_t qgroupid) { + return qgroup_create_or_destroy(fd, false, qgroupid); +} + +int btrfs_qgroup_destroy_recursive(int fd, uint64_t qgroupid) { + _cleanup_free_ uint64_t *qgroups = NULL; + uint64_t subvol_id; + int i, n, r; + + /* Destroys the specified qgroup, but unassigns it from all + * its parents first. Also, it recursively destroys all + * qgroups it is assgined to that have the same id part of the + * qgroupid as the specified group. */ + + r = btrfs_qgroupid_split(qgroupid, NULL, &subvol_id); + if (r < 0) + return r; + + n = btrfs_qgroup_find_parents(fd, qgroupid, &qgroups); + if (n < 0) + return n; + + for (i = 0; i < n; i++) { + uint64_t id; + + r = btrfs_qgroupid_split(qgroups[i], NULL, &id); + if (r < 0) + return r; + + r = btrfs_qgroup_unassign(fd, qgroupid, qgroups[i]); + if (r < 0) + return r; + + if (id != subvol_id) + continue; + + /* The parent qgroupid shares the same id part with + * us? If so, destroy it too. */ + + (void) btrfs_qgroup_destroy_recursive(fd, qgroups[i]); + } + + return btrfs_qgroup_destroy(fd, qgroupid); +} + +int btrfs_quota_scan_start(int fd) { + struct btrfs_ioctl_quota_rescan_args args = {}; + + assert(fd >= 0); + + if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN, &args) < 0) return -errno; return 0; } -int btrfs_qgroup_create(int fd, uint64_t level, uint64_t id) { - return qgroup_create_or_destroy(fd, true, level, id); +int btrfs_quota_scan_wait(int fd) { + assert(fd >= 0); + + if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_WAIT) < 0) + return -errno; + + return 0; } -int btrfs_qgroup_destroy(int fd, uint64_t level, uint64_t id) { - return qgroup_create_or_destroy(fd, false, level, id); +int btrfs_quota_scan_ongoing(int fd) { + struct btrfs_ioctl_quota_rescan_args args = {}; + + assert(fd >= 0); + + if (ioctl(fd, BTRFS_IOC_QUOTA_RESCAN_STATUS, &args) < 0) + return -errno; + + return !!args.flags; } -static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol_id, bool recursive) { +static int qgroup_assign_or_unassign(int fd, bool b, uint64_t child, uint64_t parent) { + struct btrfs_ioctl_qgroup_assign_args args = { + .assign = b, + .src = child, + .dst = parent, + }; + unsigned c; + int r; + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (r == 0) + return -ENOTTY; + + for (c = 0;; c++) { + r = ioctl(fd, BTRFS_IOC_QGROUP_ASSIGN, &args); + if (r < 0) { + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + + return -errno; + } + + if (r == 0) + return 0; + + /* If the return value is > 0, we need to request a rescan */ + + (void) btrfs_quota_scan_start(fd); + return 1; + } +} + +int btrfs_qgroup_assign(int fd, uint64_t child, uint64_t parent) { + return qgroup_assign_or_unassign(fd, true, child, parent); +} + +int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent) { + return qgroup_assign_or_unassign(fd, false, child, parent); +} + +static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol_id, BtrfsRemoveFlags flags) { struct btrfs_ioctl_search_args args = { .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, @@ -881,10 +1184,10 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol * already empty, this will just work. */ strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) >= 0) { - (void) btrfs_qgroup_destroy(fd, 0, subvol_id); + (void) btrfs_qgroup_destroy_recursive(fd, subvol_id); /* for the leaf subvolumes, the qgroup id is identical to the subvol id */ return 0; } - if (!recursive || errno != ENOTEMPTY) + if (!(flags & BTRFS_REMOVE_RECURSIVE) || errno != ENOTEMPTY) return -errno; /* OK, the subvolume is not empty, let's look for child @@ -939,7 +1242,7 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol if (isempty(ino_args.name)) /* Subvolume is in the top-level * directory of the subvolume. */ - r = subvol_remove_children(subvol_fd, p, sh->objectid, recursive); + r = subvol_remove_children(subvol_fd, p, sh->objectid, flags); else { _cleanup_close_ int child_fd = -1; @@ -951,7 +1254,7 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol if (child_fd < 0) return -errno; - r = subvol_remove_children(child_fd, p, sh->objectid, recursive); + r = subvol_remove_children(child_fd, p, sh->objectid, flags); } if (r < 0) return r; @@ -967,11 +1270,11 @@ static int subvol_remove_children(int fd, const char *subvolume, uint64_t subvol if (ioctl(fd, BTRFS_IOC_SNAP_DESTROY, &vol_args) < 0) return -errno; - (void) btrfs_qgroup_destroy(fd, 0, subvol_id); + (void) btrfs_qgroup_destroy_recursive(fd, subvol_id); return 0; } -int btrfs_subvol_remove(const char *path, bool recursive) { +int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags) { _cleanup_close_ int fd = -1; const char *subvolume; int r; @@ -986,11 +1289,194 @@ int btrfs_subvol_remove(const char *path, bool recursive) { if (fd < 0) return fd; - return subvol_remove_children(fd, subvolume, 0, recursive); + return subvol_remove_children(fd, subvolume, 0, flags); +} + +int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags) { + return subvol_remove_children(fd, subvolume, 0, flags); +} + +int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupid) { + + struct btrfs_ioctl_search_args args = { + /* Tree of quota items */ + .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, + + /* The object ID is always 0 */ + .key.min_objectid = 0, + .key.max_objectid = 0, + + /* Look precisely for the quota items */ + .key.min_type = BTRFS_QGROUP_LIMIT_KEY, + .key.max_type = BTRFS_QGROUP_LIMIT_KEY, + + /* For our qgroup */ + .key.min_offset = old_qgroupid, + .key.max_offset = old_qgroupid, + + /* No restrictions on the other components */ + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + int r; + + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return -errno; + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + const struct btrfs_qgroup_limit_item *qli = BTRFS_IOCTL_SEARCH_HEADER_BODY(sh); + struct btrfs_ioctl_qgroup_limit_args qargs; + unsigned c; + + /* Make sure we start the next search at least from this entry */ + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->objectid != 0) + continue; + if (sh->type != BTRFS_QGROUP_LIMIT_KEY) + continue; + if (sh->offset != old_qgroupid) + continue; + + /* We found the entry, now copy things over. */ + + qargs = (struct btrfs_ioctl_qgroup_limit_args) { + .qgroupid = new_qgroupid, + + .lim.max_rfer = le64toh(qli->max_rfer), + .lim.max_excl = le64toh(qli->max_excl), + .lim.rsv_rfer = le64toh(qli->rsv_rfer), + .lim.rsv_excl = le64toh(qli->rsv_excl), + + .lim.flags = le64toh(qli->flags) & (BTRFS_QGROUP_LIMIT_MAX_RFER| + BTRFS_QGROUP_LIMIT_MAX_EXCL| + BTRFS_QGROUP_LIMIT_RSV_RFER| + BTRFS_QGROUP_LIMIT_RSV_EXCL), + }; + + for (c = 0;; c++) { + if (ioctl(fd, BTRFS_IOC_QGROUP_LIMIT, &qargs) < 0) { + if (errno == EBUSY && c < 10) { + (void) btrfs_quota_scan_wait(fd); + continue; + } + return -errno; + } + + break; + } + + return 1; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + + return 0; } -int btrfs_subvol_remove_fd(int fd, const char *subvolume, bool recursive) { - return subvol_remove_children(fd, subvolume, 0, recursive); +static int copy_quota_hierarchy(int fd, uint64_t old_subvol_id, uint64_t new_subvol_id) { + _cleanup_free_ uint64_t *old_qgroups = NULL, *old_parent_qgroups = NULL; + bool copy_from_parent = false, insert_intermediary_qgroup = false; + int n_old_qgroups, n_old_parent_qgroups, r, i; + uint64_t old_parent_id; + + assert(fd >= 0); + + /* Copies a reduced form of quota information from the old to + * the new subvolume. */ + + n_old_qgroups = btrfs_qgroup_find_parents(fd, old_subvol_id, &old_qgroups); + if (n_old_qgroups <= 0) /* Nothing to copy */ + return n_old_qgroups; + + r = btrfs_subvol_get_parent(fd, old_subvol_id, &old_parent_id); + if (r < 0) + return r; + + n_old_parent_qgroups = btrfs_qgroup_find_parents(fd, old_parent_id, &old_parent_qgroups); + if (n_old_parent_qgroups < 0) + return n_old_parent_qgroups; + + for (i = 0; i < n_old_qgroups; i++) { + uint64_t id; + int j; + + r = btrfs_qgroupid_split(old_qgroups[i], NULL, &id); + if (r < 0) + return r; + + if (id == old_subvol_id) { + /* The old subvolume was member of a qgroup + * that had the same id, but a different level + * as it self. Let's set up something similar + * in the destination. */ + insert_intermediary_qgroup = true; + break; + } + + for (j = 0; j < n_old_parent_qgroups; j++) + if (old_parent_qgroups[j] == old_qgroups[i]) { + /* The old subvolume shared a common + * parent qgroup with its parent + * subvolume. Let's set up something + * similar in the destination. */ + copy_from_parent = true; + } + } + + if (!insert_intermediary_qgroup && !copy_from_parent) + return 0; + + return btrfs_subvol_auto_qgroup_fd(fd, new_subvol_id, insert_intermediary_qgroup); +} + +static int copy_subtree_quota_limits(int fd, uint64_t old_subvol, uint64_t new_subvol) { + uint64_t old_subtree_qgroup, new_subtree_qgroup; + bool changed; + int r; + + /* First copy the leaf limits */ + r = btrfs_qgroup_copy_limits(fd, old_subvol, new_subvol); + if (r < 0) + return r; + changed = r > 0; + + /* Then, try to copy the subtree limits, if there are any. */ + r = btrfs_subvol_find_subtree_qgroup(fd, old_subvol, &old_subtree_qgroup); + if (r < 0) + return r; + if (r == 0) + return changed; + + r = btrfs_subvol_find_subtree_qgroup(fd, new_subvol, &new_subtree_qgroup); + if (r < 0) + return r; + if (r == 0) + return changed; + + r = btrfs_qgroup_copy_limits(fd, old_subtree_qgroup, new_subtree_qgroup); + if (r != 0) + return r; + + return changed; } static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolume, uint64_t old_subvol_id, BtrfsSnapshotFlags flags) { @@ -1021,12 +1507,12 @@ static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolum assert(subvolume); strncpy(vol_args.name, subvolume, sizeof(vol_args.name)-1); - vol_args.fd = old_fd; if (ioctl(new_fd, BTRFS_IOC_SNAP_CREATE_V2, &vol_args) < 0) return -errno; - if (!(flags & BTRFS_SNAPSHOT_RECURSIVE)) + if (!(flags & BTRFS_SNAPSHOT_RECURSIVE) && + !(flags & BTRFS_SNAPSHOT_QUOTA)) return 0; if (old_subvol_id == 0) { @@ -1039,6 +1525,17 @@ static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolum if (r < 0) return r; + if (flags & BTRFS_SNAPSHOT_QUOTA) + (void) copy_quota_hierarchy(new_fd, old_subvol_id, new_subvol_id); + + if (!(flags & BTRFS_SNAPSHOT_RECURSIVE)) { + + if (flags & BTRFS_SNAPSHOT_QUOTA) + (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); + + return 0; + } + args.key.min_offset = args.key.max_offset = old_subvol_id; while (btrfs_ioctl_search_args_compare(&args) <= 0) { @@ -1156,6 +1653,9 @@ static int subvol_snapshot_children(int old_fd, int new_fd, const char *subvolum break; } + if (flags & BTRFS_SNAPSHOT_QUOTA) + (void) copy_subtree_quota_limits(new_fd, old_subvol_id, new_subvol_id); + return 0; } @@ -1180,14 +1680,14 @@ int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlag r = copy_directory_fd(old_fd, new_path, true); if (r < 0) { - btrfs_subvol_remove(new_path, false); + (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); return r; } if (flags & BTRFS_SNAPSHOT_READ_ONLY) { r = btrfs_subvol_set_read_only(new_path, true); if (r < 0) { - btrfs_subvol_remove(new_path, false); + (void) btrfs_subvol_remove(new_path, BTRFS_REMOVE_QUOTA); return r; } } @@ -1218,3 +1718,302 @@ int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnaps return btrfs_subvol_snapshot_fd(old_fd, new_path, flags); } + +int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret) { + + struct btrfs_ioctl_search_args args = { + /* Tree of quota items */ + .key.tree_id = BTRFS_QUOTA_TREE_OBJECTID, + + /* Look precisely for the quota relation items */ + .key.min_type = BTRFS_QGROUP_RELATION_KEY, + .key.max_type = BTRFS_QGROUP_RELATION_KEY, + + /* No restrictions on the other components */ + .key.min_offset = 0, + .key.max_offset = (uint64_t) -1, + + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + + _cleanup_free_ uint64_t *items = NULL; + size_t n_items = 0, n_allocated = 0; + int r; + + assert(fd >= 0); + assert(ret); + + if (qgroupid == 0) { + r = btrfs_subvol_get_id_fd(fd, &qgroupid); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.key.min_objectid = args.key.max_objectid = qgroupid; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return -errno; + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + + /* Make sure we start the next search at least from this entry */ + btrfs_ioctl_search_args_set(&args, sh); + + if (sh->type != BTRFS_QGROUP_RELATION_KEY) + continue; + if (sh->offset < sh->objectid) + continue; + if (sh->objectid != qgroupid) + continue; + + if (!GREEDY_REALLOC(items, n_allocated, n_items+1)) + return -ENOMEM; + + items[n_items++] = sh->offset; + } + + /* Increase search key by one, to read the next item, if we can. */ + if (!btrfs_ioctl_search_args_inc(&args)) + break; + } + + if (n_items <= 0) { + *ret = NULL; + return 0; + } + + *ret = items; + items = NULL; + + return (int) n_items; +} + +int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool insert_intermediary_qgroup) { + _cleanup_free_ uint64_t *qgroups = NULL; + uint64_t parent_subvol; + bool changed = false; + int n = 0, r; + + assert(fd >= 0); + + /* + * Sets up the specified subvolume's qgroup automatically in + * one of two ways: + * + * If insert_intermediary_qgroup is false, the subvolume's + * leaf qgroup will be assigned to the same parent qgroups as + * the subvolume's parent subvolume. + * + * If insert_intermediary_qgroup is true a new intermediary + * higher-level qgroup is created, with a higher level number, + * but reusing the id of the subvolume. The level number is + * picked as one smaller than the lowest level qgroup the + * parent subvolume is a member of. If the parent subvolume's + * leaf qgroup is assigned to no higher-level qgroup a new + * qgroup of level 255 is created instead. Either way, the new + * qgroup is then assigned to the parent's higher-level + * qgroup, and the subvolume itself is assigned to it. + * + * If the subvolume is already assigned to a higher level + * qgroup, no operation is executed. + * + * Effectively this means: regardless if + * insert_intermediary_qgroup is true or not, after this + * function is invoked the subvolume will be accounted within + * the same qgroups as the parent. However, if it is true, it + * will also get its own higher-level qgroup, which may in + * turn be used by subvolumes created beneath this subvolume + * later on. + * + * This hence defines a simple default qgroup setup for + * subvolumes, as long as this function is invoked on each + * created subvolume: each subvolume is always accounting + * together with its immediate parents. Optionally, if + * insert_intermediary_qgroup is true, it will also get a + * qgroup that then includes all its own child subvolumes. + */ + + if (subvol_id == 0) { + r = btrfs_is_subvol(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } + + n = btrfs_qgroup_find_parents(fd, subvol_id, &qgroups); + if (n < 0) + return n; + if (n > 0) /* already parent qgroups set up, let's bail */ + return 0; + + r = btrfs_subvol_get_parent(fd, subvol_id, &parent_subvol); + if (r < 0) + return r; + + qgroups = mfree(qgroups); + n = btrfs_qgroup_find_parents(fd, parent_subvol, &qgroups); + if (n < 0) + return n; + + if (insert_intermediary_qgroup) { + uint64_t lowest = 256, new_qgroupid; + bool created = false; + int i; + + /* Determine the lowest qgroup that the parent + * subvolume is assigned to. */ + + for (i = 0; i < n; i++) { + uint64_t level; + + r = btrfs_qgroupid_split(qgroups[i], &level, NULL); + if (r < 0) + return r; + + if (level < lowest) + lowest = level; + } + + if (lowest <= 1) /* There are no levels left we could use insert an intermediary qgroup at */ + return -EBUSY; + + r = btrfs_qgroupid_make(lowest - 1, subvol_id, &new_qgroupid); + if (r < 0) + return r; + + /* Create the new intermediary group, unless it already exists */ + r = btrfs_qgroup_create(fd, new_qgroupid); + if (r < 0 && r != -EEXIST) + return r; + if (r >= 0) + changed = created = true; + + for (i = 0; i < n; i++) { + r = btrfs_qgroup_assign(fd, new_qgroupid, qgroups[i]); + if (r < 0 && r != -EEXIST) { + if (created) + (void) btrfs_qgroup_destroy_recursive(fd, new_qgroupid); + + return r; + } + if (r >= 0) + changed = true; + } + + r = btrfs_qgroup_assign(fd, subvol_id, new_qgroupid); + if (r < 0 && r != -EEXIST) { + if (created) + (void) btrfs_qgroup_destroy_recursive(fd, new_qgroupid); + return r; + } + if (r >= 0) + changed = true; + + } else { + int i; + + /* Assign our subvolume to all the same qgroups as the parent */ + + for (i = 0; i < n; i++) { + r = btrfs_qgroup_assign(fd, subvol_id, qgroups[i]); + if (r < 0 && r != -EEXIST) + return r; + if (r >= 0) + changed = true; + } + } + + return changed; +} + +int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup) { + _cleanup_close_ int fd = -1; + + fd = open(path, O_RDONLY|O_NOCTTY|O_CLOEXEC|O_DIRECTORY); + if (fd < 0) + return -errno; + + return btrfs_subvol_auto_qgroup_fd(fd, subvol_id, create_intermediary_qgroup); +} + +int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret) { + + struct btrfs_ioctl_search_args args = { + /* Tree of tree roots */ + .key.tree_id = BTRFS_ROOT_TREE_OBJECTID, + + /* Look precisely for the subvolume items */ + .key.min_type = BTRFS_ROOT_BACKREF_KEY, + .key.max_type = BTRFS_ROOT_BACKREF_KEY, + + /* No restrictions on the other components */ + .key.min_offset = 0, + .key.max_offset = (uint64_t) -1, + + .key.min_transid = 0, + .key.max_transid = (uint64_t) -1, + }; + int r; + + assert(fd >= 0); + assert(ret); + + if (subvol_id == 0) { + r = btrfs_subvol_get_id_fd(fd, &subvol_id); + if (r < 0) + return r; + } else { + r = btrfs_is_filesystem(fd); + if (r < 0) + return r; + if (!r) + return -ENOTTY; + } + + args.key.min_objectid = args.key.max_objectid = subvol_id; + + while (btrfs_ioctl_search_args_compare(&args) <= 0) { + const struct btrfs_ioctl_search_header *sh; + unsigned i; + + args.key.nr_items = 256; + if (ioctl(fd, BTRFS_IOC_TREE_SEARCH, &args) < 0) + return -errno; + + if (args.key.nr_items <= 0) + break; + + FOREACH_BTRFS_IOCTL_SEARCH_HEADER(i, sh, args) { + + if (sh->type != BTRFS_ROOT_BACKREF_KEY) + continue; + if (sh->objectid != subvol_id) + continue; + + *ret = sh->offset; + return 0; + } + } + + return -ENXIO; +} diff --git a/src/basic/btrfs-util.h b/src/basic/btrfs-util.h index ad7c7009ab..fc9efd72d5 100644 --- a/src/basic/btrfs-util.h +++ b/src/basic/btrfs-util.h @@ -47,45 +47,82 @@ typedef enum BtrfsSnapshotFlags { BTRFS_SNAPSHOT_FALLBACK_COPY = 1, BTRFS_SNAPSHOT_READ_ONLY = 2, BTRFS_SNAPSHOT_RECURSIVE = 4, + BTRFS_SNAPSHOT_QUOTA = 8, } BtrfsSnapshotFlags; +typedef enum BtrfsRemoveFlags { + BTRFS_REMOVE_RECURSIVE = 1, + BTRFS_REMOVE_QUOTA = 2, +} BtrfsRemoveFlags; + int btrfs_is_filesystem(int fd); int btrfs_is_subvol(int fd); +int btrfs_reflink(int infd, int outfd); +int btrfs_clone_range(int infd, uint64_t in_offset, int ofd, uint64_t out_offset, uint64_t sz); + +int btrfs_get_block_device_fd(int fd, dev_t *dev); +int btrfs_get_block_device(const char *path, dev_t *dev); + +int btrfs_defrag_fd(int fd); +int btrfs_defrag(const char *p); + +int btrfs_quota_enable_fd(int fd, bool b); +int btrfs_quota_enable(const char *path, bool b); + +int btrfs_quota_scan_start(int fd); +int btrfs_quota_scan_wait(int fd); +int btrfs_quota_scan_ongoing(int fd); + +int btrfs_resize_loopback_fd(int fd, uint64_t size, bool grow_only); +int btrfs_resize_loopback(const char *path, uint64_t size, bool grow_only); + int btrfs_subvol_make(const char *path); int btrfs_subvol_make_label(const char *path); int btrfs_subvol_snapshot_fd(int old_fd, const char *new_path, BtrfsSnapshotFlags flags); int btrfs_subvol_snapshot(const char *old_path, const char *new_path, BtrfsSnapshotFlags flags); +int btrfs_subvol_remove(const char *path, BtrfsRemoveFlags flags); +int btrfs_subvol_remove_fd(int fd, const char *subvolume, BtrfsRemoveFlags flags); + int btrfs_subvol_set_read_only_fd(int fd, bool b); int btrfs_subvol_set_read_only(const char *path, bool b); int btrfs_subvol_get_read_only_fd(int fd); + int btrfs_subvol_get_id(int fd, const char *subvolume, uint64_t *ret); int btrfs_subvol_get_id_fd(int fd, uint64_t *ret); -int btrfs_subvol_get_info_fd(int fd, BtrfsSubvolInfo *info); -int btrfs_subvol_get_quota_fd(int fd, BtrfsQuotaInfo *quota); +int btrfs_subvol_get_parent(int fd, uint64_t subvol_id, uint64_t *ret); -int btrfs_reflink(int infd, int outfd); -int btrfs_clone_range(int infd, uint64_t in_offset, int ofd, uint64_t out_offset, uint64_t sz); +int btrfs_subvol_get_info_fd(int fd, uint64_t subvol_id, BtrfsSubvolInfo *info); -int btrfs_get_block_device_fd(int fd, dev_t *dev); -int btrfs_get_block_device(const char *path, dev_t *dev); +int btrfs_subvol_find_subtree_qgroup(int fd, uint64_t subvol_id, uint64_t *ret); -int btrfs_defrag_fd(int fd); -int btrfs_defrag(const char *p); +int btrfs_subvol_get_subtree_quota(const char *path, uint64_t subvol_id, BtrfsQuotaInfo *quota); +int btrfs_subvol_get_subtree_quota_fd(int fd, uint64_t subvol_id, BtrfsQuotaInfo *quota); -int btrfs_quota_enable_fd(int fd, bool b); -int btrfs_quota_enable(const char *path, bool b); +int btrfs_subvol_set_subtree_quota_limit(const char *path, uint64_t subvol_id, uint64_t referenced_max); +int btrfs_subvol_set_subtree_quota_limit_fd(int fd, uint64_t subvol_id, uint64_t referenced_max); -int btrfs_quota_limit_fd(int fd, uint64_t referenced_max); -int btrfs_quota_limit(const char *path, uint64_t referenced_max); +int btrfs_subvol_auto_qgroup_fd(int fd, uint64_t subvol_id, bool new_qgroup); +int btrfs_subvol_auto_qgroup(const char *path, uint64_t subvol_id, bool create_intermediary_qgroup); -int btrfs_resize_loopback_fd(int fd, uint64_t size, bool grow_only); -int btrfs_resize_loopback(const char *path, uint64_t size, bool grow_only); +int btrfs_qgroupid_make(uint64_t level, uint64_t id, uint64_t *ret); +int btrfs_qgroupid_split(uint64_t qgroupid, uint64_t *level, uint64_t *id); + +int btrfs_qgroup_create(int fd, uint64_t qgroupid); +int btrfs_qgroup_destroy(int fd, uint64_t qgroupid); +int btrfs_qgroup_destroy_recursive(int fd, uint64_t qgroupid); + +int btrfs_qgroup_set_limit_fd(int fd, uint64_t qgroupid, uint64_t referenced_max); +int btrfs_qgroup_set_limit(const char *path, uint64_t qgroupid, uint64_t referenced_max); + +int btrfs_qgroup_copy_limits(int fd, uint64_t old_qgroupid, uint64_t new_qgroupid); + +int btrfs_qgroup_assign(int fd, uint64_t child, uint64_t parent); +int btrfs_qgroup_unassign(int fd, uint64_t child, uint64_t parent); -int btrfs_subvol_remove(const char *path, bool recursive); -int btrfs_subvol_remove_fd(int fd, const char *subvolume, bool recursive); +int btrfs_qgroup_find_parents(int fd, uint64_t qgroupid, uint64_t **ret); -int btrfs_qgroup_create(int fd, uint64_t level, uint64_t id); -int btrfs_qgroup_destroy(int fd, uint64_t level, uint64_t id); +int btrfs_qgroup_get_quota_fd(int fd, uint64_t qgroupid, BtrfsQuotaInfo *quota); +int btrfs_qgroup_get_quota(const char *path, uint64_t qgroupid, BtrfsQuotaInfo *quota); diff --git a/src/basic/missing.h b/src/basic/missing.h index 70d6c8308e..306c56a156 100644 --- a/src/basic/missing.h +++ b/src/basic/missing.h @@ -494,6 +494,10 @@ struct btrfs_ioctl_quota_ctl_args { #define BTRFS_QGROUP_LIMIT_KEY 244 #endif +#ifndef BTRFS_QGROUP_RELATION_KEY +#define BTRFS_QGROUP_RELATION_KEY 246 +#endif + #ifndef BTRFS_ROOT_BACKREF_KEY #define BTRFS_ROOT_BACKREF_KEY 144 #endif diff --git a/src/basic/path-util.c b/src/basic/path-util.c index 5cbfc145a4..1039623305 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -796,14 +796,11 @@ bool paths_check_timestamp(const char* const* paths, usec_t *timestamp, bool upd return changed; } -int fsck_exists(const char *fstype) { +static int binary_is_good(const char *binary) { _cleanup_free_ char *p = NULL, *d = NULL; - const char *checker; int r; - checker = strjoina("fsck.", fstype); - - r = find_binary(checker, true, &p); + r = find_binary(binary, true, &p); if (r < 0) return r; @@ -820,6 +817,22 @@ int fsck_exists(const char *fstype) { return 0; } +int fsck_exists(const char *fstype) { + const char *checker; + + checker = strjoina("fsck.", fstype); + + return binary_is_good(checker); +} + +int mkfs_exists(const char *fstype) { + const char *mkfs; + + mkfs = strjoina("mkfs.", fstype); + + return binary_is_good(mkfs); +} + char *prefix_root(const char *root, const char *path) { char *n, *p; size_t l; diff --git a/src/basic/path-util.h b/src/basic/path-util.h index 1eac89c51b..71e25f1e57 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -63,6 +63,7 @@ int find_binary(const char *name, bool local, char **filename); bool paths_check_timestamp(const char* const* paths, usec_t *paths_ts_usec, bool update); int fsck_exists(const char *fstype); +int mkfs_exists(const char *fstype); /* Iterates through the path prefixes of the specified path, going up * the tree, to root. Also returns "" (and not "/"!) for the root diff --git a/src/basic/rm-rf.c b/src/basic/rm-rf.c index dbbe817684..2ef63799d7 100644 --- a/src/basic/rm-rf.c +++ b/src/basic/rm-rf.c @@ -120,7 +120,7 @@ int rm_rf_children(int fd, RemoveFlags flags, struct stat *root_dev) { /* This could be a subvolume, try to remove it */ - r = btrfs_subvol_remove_fd(fd, de->d_name, true); + r = btrfs_subvol_remove_fd(fd, de->d_name, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); if (r < 0) { if (r != -ENOTTY && r != -EINVAL) { if (ret == 0) @@ -178,7 +178,7 @@ int rm_rf(const char *path, RemoveFlags flags) { if ((flags & (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) == (REMOVE_SUBVOLUME|REMOVE_ROOT|REMOVE_PHYSICAL)) { /* Try to remove as subvolume first */ - r = btrfs_subvol_remove(path, true); + r = btrfs_subvol_remove(path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); if (r >= 0) return r; diff --git a/src/basic/util.c b/src/basic/util.c index 3989b415fa..ec3b360ffe 100644 --- a/src/basic/util.c +++ b/src/basic/util.c @@ -2381,25 +2381,16 @@ bool is_device_path(const char *path) { int dir_is_empty(const char *path) { _cleanup_closedir_ DIR *d; + struct dirent *de; d = opendir(path); if (!d) return -errno; - for (;;) { - struct dirent *de; - - errno = 0; - de = readdir(d); - if (!de && errno != 0) - return -errno; - - if (!de) - return 1; + FOREACH_DIRENT(de, d, return -errno) + return 0; - if (!hidden_file(de->d_name)) - return 0; - } + return 1; } char* dirname_malloc(const char *path) { diff --git a/src/basic/util.h b/src/basic/util.h index ff39eae715..a3ebb987e4 100644 --- a/src/basic/util.h +++ b/src/basic/util.h @@ -358,6 +358,14 @@ bool is_device_path(const char *path); int dir_is_empty(const char *path); char* dirname_malloc(const char *path); +static inline int dir_is_populated(const char *path) { + int r; + r = dir_is_empty(path); + if (r < 0) + return r; + return !r; +} + char* lookup_uid(uid_t uid); char* getlogname_malloc(void); char* getusername_malloc(void); diff --git a/src/core/dbus-execute.c b/src/core/dbus-execute.c index 6712b86232..b8da66c985 100644 --- a/src/core/dbus-execute.c +++ b/src/core/dbus-execute.c @@ -1173,7 +1173,6 @@ int bus_exec_context_set_transient_property( unit_write_drop_in_private_format(u, mode, name, "Environment=%s\n", joined); } - } return 1; @@ -1262,6 +1261,10 @@ int bus_exec_context_set_transient_property( if (r < 0) return r; + r = sd_bus_message_exit_container(message); + if (r < 0) + return r; + r = fflush_and_check(f); if (r < 0) return r; @@ -1279,10 +1282,6 @@ int bus_exec_context_set_transient_property( } } - r = sd_bus_message_exit_container(message); - if (r < 0) - return r; - return 1; } else if (STR_IN_SET(name, "ReadWriteDirectories", "ReadOnlyDirectories", "InaccessibleDirectories")) { diff --git a/src/import/export-tar.c b/src/import/export-tar.c index 43fa9d1b03..a623745f5f 100644 --- a/src/import/export-tar.c +++ b/src/import/export-tar.c @@ -78,7 +78,7 @@ TarExport *tar_export_unref(TarExport *e) { } if (e->temp_path) { - (void) btrfs_subvol_remove(e->temp_path, false); + (void) btrfs_subvol_remove(e->temp_path, BTRFS_REMOVE_QUOTA); free(e->temp_path); } @@ -283,7 +283,7 @@ int tar_export_start(TarExport *e, const char *path, int fd, ImportCompressType if (e->st.st_ino == 256) { /* might be a btrfs subvolume? */ BtrfsQuotaInfo q; - r = btrfs_subvol_get_quota_fd(sfd, &q); + r = btrfs_subvol_get_subtree_quota_fd(sfd, 0, &q); if (r >= 0) e->quota_referenced = q.referenced; diff --git a/src/import/import-tar.c b/src/import/import-tar.c index d2bfb30238..5c288d438e 100644 --- a/src/import/import-tar.c +++ b/src/import/import-tar.c @@ -235,6 +235,8 @@ static int tar_import_fork_tar(TarImport *i) { return log_error_errno(errno, "Failed to create directory %s: %m", i->temp_path); } else if (r < 0) return log_error_errno(errno, "Failed to create subvolume %s: %m", i->temp_path); + else + (void) import_assign_pool_quota_and_warn(i->temp_path); i->tar_fd = import_fork_tar_x(i->temp_path, &i->tar_pid); if (i->tar_fd < 0) diff --git a/src/import/pull-common.c b/src/import/pull-common.c index 1ddb48e03f..edebb91556 100644 --- a/src/import/pull-common.c +++ b/src/import/pull-common.c @@ -138,7 +138,7 @@ int pull_make_local_copy(const char *final, const char *image_root, const char * if (force_local) (void) rm_rf(p, REMOVE_ROOT|REMOVE_PHYSICAL|REMOVE_SUBVOLUME); - r = btrfs_subvol_snapshot(final, p, 0); + r = btrfs_subvol_snapshot(final, p, BTRFS_SNAPSHOT_QUOTA); if (r == -ENOTTY) { r = copy_tree(final, p, false); if (r < 0) @@ -366,9 +366,10 @@ int pull_verify(PullJob *main_job, log_info("SHA256 checksum of %s is valid.", main_job->url); - assert(!settings_job || settings_job->state == PULL_JOB_DONE); + assert(!settings_job || IN_SET(settings_job->state, PULL_JOB_DONE, PULL_JOB_FAILED)); if (settings_job && + settings_job->state == PULL_JOB_DONE && settings_job->error == 0 && !settings_job->etag_exists) { diff --git a/src/import/pull-dkr.c b/src/import/pull-dkr.c index 0dab184af1..84211d282b 100644 --- a/src/import/pull-dkr.c +++ b/src/import/pull-dkr.c @@ -490,10 +490,16 @@ static int dkr_pull_make_local_copy(DkrPull *i, DkrPullVersion version) { return r; if (version == DKR_PULL_V2) { - char **k = NULL; + char **k; + STRV_FOREACH(k, i->ancestry) { - _cleanup_free_ char *d = strjoin(i->image_root, "/.dkr-", *k, NULL); - r = btrfs_subvol_remove(d, false); + _cleanup_free_ char *d; + + d = strjoin(i->image_root, "/.dkr-", *k, NULL); + if (!d) + return -ENOMEM; + + r = btrfs_subvol_remove(d, BTRFS_REMOVE_QUOTA); if (r < 0) return r; } @@ -531,12 +537,14 @@ static int dkr_pull_job_on_open_disk(PullJob *j) { const char *base_path; base_path = strjoina(i->image_root, "/.dkr-", base); - r = btrfs_subvol_snapshot(base_path, i->temp_path, BTRFS_SNAPSHOT_FALLBACK_COPY); + r = btrfs_subvol_snapshot(base_path, i->temp_path, BTRFS_SNAPSHOT_FALLBACK_COPY|BTRFS_SNAPSHOT_QUOTA); } else r = btrfs_subvol_make(i->temp_path); if (r < 0) return log_error_errno(r, "Failed to make btrfs subvolume %s: %m", i->temp_path); + (void) import_assign_pool_quota_and_warn(i->temp_path); + j->disk_fd = import_fork_tar_x(i->temp_path, &i->tar_pid); if (j->disk_fd < 0) return j->disk_fd; diff --git a/src/import/pull-raw.c b/src/import/pull-raw.c index 0e77197e34..3e13f4ea9a 100644 --- a/src/import/pull-raw.c +++ b/src/import/pull-raw.c @@ -349,9 +349,9 @@ static int raw_pull_make_local_copy(RawPull *i) { if (r == -EEXIST) log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings); else if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to copy settings files %s: %m", local_settings); - - log_info("Create new settings file '%s.nspawn'", i->local); + log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings); + else + log_info("Created new settings file '%s.nspawn'", i->local); } return 0; diff --git a/src/import/pull-tar.c b/src/import/pull-tar.c index 563765d83d..bd35f1b842 100644 --- a/src/import/pull-tar.c +++ b/src/import/pull-tar.c @@ -247,9 +247,9 @@ static int tar_pull_make_local_copy(TarPull *i) { if (r == -EEXIST) log_warning_errno(r, "Settings file %s already exists, not replacing.", local_settings); else if (r < 0 && r != -ENOENT) - log_warning_errno(r, "Failed to copy settings files %s: %m", local_settings); - - log_info("Create new settings file '%s.nspawn'", i->local); + log_warning_errno(r, "Failed to copy settings files %s, ignoring: %m", local_settings); + else + log_info("Created new settings file '%s.nspawn'", i->local); } return 0; @@ -410,6 +410,8 @@ static int tar_pull_job_on_open_disk_tar(PullJob *j) { return log_error_errno(errno, "Failed to create directory %s: %m", i->temp_path); } else if (r < 0) return log_error_errno(errno, "Failed to create subvolume %s: %m", i->temp_path); + else + (void) import_assign_pool_quota_and_warn(i->temp_path); j->disk_fd = import_fork_tar_x(i->temp_path, &i->tar_pid); if (j->disk_fd < 0) diff --git a/src/login/logind-session.c b/src/login/logind-session.c index f5fe030b07..fa82e444ef 100644 --- a/src/login/logind-session.c +++ b/src/login/logind-session.c @@ -1049,9 +1049,13 @@ error: } void session_restore_vt(Session *s) { + + static const struct vt_mode mode = { + .mode = VT_AUTO, + }; + _cleanup_free_ char *utf8 = NULL; - int vt, kb = K_XLATE; - struct vt_mode mode = { 0 }; + int vt, kb, old_fd; /* We need to get a fresh handle to the virtual terminal, * since the old file-descriptor is potentially in a hung-up @@ -1059,7 +1063,7 @@ void session_restore_vt(Session *s) { * little dance to avoid having the terminal be available * for reuse before we've cleaned it up. */ - int old_fd = s->vtfd; + old_fd = s->vtfd; s->vtfd = -1; vt = session_open_vt(s); @@ -1072,13 +1076,13 @@ void session_restore_vt(Session *s) { if (read_one_line_file("/sys/module/vt/parameters/default_utf8", &utf8) >= 0 && *utf8 == '1') kb = K_UNICODE; + else + kb = K_XLATE; (void) ioctl(vt, KDSKBMODE, kb); - mode.mode = VT_AUTO; (void) ioctl(vt, VT_SETMODE, &mode); - - fchown(vt, 0, -1); + (void) fchown(vt, 0, (gid_t) -1); s->vtfd = safe_close(s->vtfd); } diff --git a/src/machine/machinectl.c b/src/machine/machinectl.c index 0a21ab4415..d7e0395690 100644 --- a/src/machine/machinectl.c +++ b/src/machine/machinectl.c @@ -2382,7 +2382,7 @@ static int set_limit(int argc, char *argv[], void *userdata) { uint64_t limit; int r; - if (streq(argv[argc-1], "-")) + if (STR_IN_SET(argv[argc-1], "-", "none", "infinity")) limit = (uint64_t) -1; else { r = parse_size(argv[argc-1], 1024, &limit); diff --git a/src/machine/machined-dbus.c b/src/machine/machined-dbus.c index 41bb106d28..6e4c72e8a9 100644 --- a/src/machine/machined-dbus.c +++ b/src/machine/machined-dbus.c @@ -79,7 +79,7 @@ static int property_get_pool_usage( if (fd >= 0) { BtrfsQuotaInfo q; - if (btrfs_subvol_get_quota_fd(fd, &q) >= 0) + if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) usage = q.referenced; } @@ -115,7 +115,7 @@ static int property_get_pool_limit( if (fd >= 0) { BtrfsQuotaInfo q; - if (btrfs_subvol_get_quota_fd(fd, &q) >= 0) + if (btrfs_subvol_get_subtree_quota_fd(fd, 0, &q) >= 0) size = q.referenced_max; } @@ -831,7 +831,9 @@ static int method_set_pool_limit(sd_bus_message *message, void *userdata, sd_bus if (r < 0 && r != -ENODEV) /* ignore ENODEV, as that's what is returned if the file system is not on loopback */ return sd_bus_error_set_errnof(error, r, "Failed to adjust loopback limit: %m"); - r = btrfs_quota_limit("/var/lib/machines", limit); + (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, limit); + + r = btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, limit); if (r == -ENOTTY) return sd_bus_error_setf(error, SD_BUS_ERROR_NOT_SUPPORTED, "Quota is only supported on btrfs."); if (r < 0) diff --git a/src/nspawn/nspawn-settings.c b/src/nspawn/nspawn-settings.c index b920391b38..f088884776 100644 --- a/src/nspawn/nspawn-settings.c +++ b/src/nspawn/nspawn-settings.c @@ -85,6 +85,26 @@ Settings* settings_free(Settings *s) { return NULL; } +bool settings_private_network(Settings *s) { + assert(s); + + return + s->private_network > 0 || + s->network_veth > 0 || + s->network_bridge || + s->network_interfaces || + s->network_macvlan || + s->network_ipvlan; +} + +bool settings_network_veth(Settings *s) { + assert(s); + + return + s->network_veth > 0 || + s->network_bridge; +} + DEFINE_CONFIG_PARSE_ENUM(config_parse_volatile_mode, volatile_mode, VolatileMode, "Failed to parse volatile mode"); int config_parse_expose_port( diff --git a/src/nspawn/nspawn-settings.h b/src/nspawn/nspawn-settings.h index 4cec40c1b7..16e8c54508 100644 --- a/src/nspawn/nspawn-settings.h +++ b/src/nspawn/nspawn-settings.h @@ -75,6 +75,9 @@ typedef struct Settings { int settings_load(FILE *f, const char *path, Settings **ret); Settings* settings_free(Settings *s); +bool settings_network_veth(Settings *s); +bool settings_private_network(Settings *s); + DEFINE_TRIVIAL_CLEANUP_FUNC(Settings*, settings_free); const struct ConfigPerfItem* nspawn_gperf_lookup(const char *key, unsigned length); diff --git a/src/nspawn/nspawn.c b/src/nspawn/nspawn.c index fca2b72edd..99e24cf4ff 100644 --- a/src/nspawn/nspawn.c +++ b/src/nspawn/nspawn.c @@ -2912,11 +2912,17 @@ static int load_settings(void) { } if ((arg_settings_mask & SETTING_CAPABILITY) == 0) { + uint64_t plus; - if (!arg_settings_trusted && settings->capability != 0) - log_warning("Ignoring Capability= setting, file %s is not trusted.", p); - else - arg_retain |= settings->capability; + plus = settings->capability; + if (settings_private_network(settings)) + plus |= (1ULL << CAP_NET_ADMIN); + + if (!arg_settings_trusted && plus != 0) { + if (settings->capability != 0) + log_warning("Ignoring Capability= setting, file %s is not trusted.", p); + } else + arg_retain |= plus; arg_retain &= ~settings->drop_capability; } @@ -2972,6 +2978,9 @@ static int load_settings(void) { if (!arg_settings_trusted) log_warning("Ignoring network settings, file %s is not trusted.", p); else { + arg_network_veth = settings_private_network(settings); + arg_private_network = settings_private_network(settings); + strv_free(arg_network_interfaces); arg_network_interfaces = settings->network_interfaces; settings->network_interfaces = NULL; @@ -2987,10 +2996,6 @@ static int load_settings(void) { free(arg_network_bridge); arg_network_bridge = settings->network_bridge; settings->network_bridge = NULL; - - arg_network_veth = settings->network_veth > 0 || settings->network_bridge; - - arg_private_network = true; /* all these settings imply private networking */ } } @@ -3096,7 +3101,7 @@ int main(int argc, char *argv[]) { goto finish; } - r = btrfs_subvol_snapshot(arg_directory, np, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE); + r = btrfs_subvol_snapshot(arg_directory, np, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); if (r < 0) { log_error_errno(r, "Failed to create snapshot %s from %s: %m", np, arg_directory); goto finish; @@ -3120,7 +3125,7 @@ int main(int argc, char *argv[]) { } if (arg_template) { - r = btrfs_subvol_snapshot(arg_template, arg_directory, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE); + r = btrfs_subvol_snapshot(arg_template, arg_directory, (arg_read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); if (r == -EEXIST) { if (!arg_quiet) log_info("Directory %s already exists, not populating from template %s.", arg_directory, arg_template); @@ -3143,10 +3148,9 @@ int main(int argc, char *argv[]) { } else { const char *p; - p = strjoina(arg_directory, - argc > optind && path_is_absolute(argv[optind]) ? argv[optind] : "/usr/bin/"); - if (access(p, F_OK) < 0) { - log_error("Directory %s lacks the binary to execute or doesn't look like a binary tree. Refusing.", arg_directory); + p = strjoina(arg_directory, "/usr/"); + if (laccess(p, F_OK) < 0) { + log_error("Directory %s doesn't look like it has an OS tree. Refusing.", arg_directory); r = -EINVAL; goto finish; } @@ -3591,7 +3595,7 @@ finish: if (remove_subvol && arg_directory) { int k; - k = btrfs_subvol_remove(arg_directory, true); + k = btrfs_subvol_remove(arg_directory, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); if (k < 0) log_warning_errno(k, "Cannot remove subvolume '%s', ignoring: %m", arg_directory); } diff --git a/src/shared/import-util.c b/src/shared/import-util.c index 001a8a37e8..56388d5dd6 100644 --- a/src/shared/import-util.c +++ b/src/shared/import-util.c @@ -19,6 +19,7 @@ along with systemd; If not, see <http://www.gnu.org/licenses/>. ***/ +#include "btrfs-util.h" #include "util.h" #include "import-util.h" @@ -201,3 +202,29 @@ bool dkr_id_is_valid(const char *id) { return true; } + +int import_assign_pool_quota_and_warn(const char *path) { + int r; + + r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true); + if (r == -ENOTTY) { + log_debug_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, as directory is not on btrfs or not a subvolume. Ignoring."); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines: %m"); + if (r > 0) + log_info("Set up default quota hierarchy for /var/lib/machines."); + + r = btrfs_subvol_auto_qgroup(path, 0, true); + if (r == -ENOTTY) { + log_debug_errno(r, "Failed to set up quota hierarchy for %s, as directory is not on btrfs or not a subvolume. Ignoring.", path); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to set up default quota hierarchy for %s: %m", path); + if (r > 0) + log_info("Set up default quota hierarchy for %s.", path); + + return 0; +} diff --git a/src/shared/import-util.h b/src/shared/import-util.h index 7bf7d4ca40..9120a5119f 100644 --- a/src/shared/import-util.h +++ b/src/shared/import-util.h @@ -47,3 +47,5 @@ bool dkr_id_is_valid(const char *id); bool dkr_ref_is_valid(const char *ref); bool dkr_digest_is_valid(const char *digest); #define dkr_tag_is_valid(tag) filename_is_valid(tag) + +int import_assign_pool_quota_and_warn(const char *path); diff --git a/src/shared/machine-image.c b/src/shared/machine-image.c index 9c1e4d5e13..8ed3ad7f44 100644 --- a/src/shared/machine-image.c +++ b/src/shared/machine-image.c @@ -176,11 +176,10 @@ static int image_make( return r; if (r) { BtrfsSubvolInfo info; - BtrfsQuotaInfo quota; /* It's a btrfs subvolume */ - r = btrfs_subvol_get_info_fd(fd, &info); + r = btrfs_subvol_get_info_fd(fd, 0, &info); if (r < 0) return r; @@ -195,13 +194,17 @@ static int image_make( if (r < 0) return r; - r = btrfs_subvol_get_quota_fd(fd, "a); - if (r >= 0) { - (*ret)->usage = quota.referenced; - (*ret)->usage_exclusive = quota.exclusive; + if (btrfs_quota_scan_ongoing(fd) == 0) { + BtrfsQuotaInfo quota; - (*ret)->limit = quota.referenced_max; - (*ret)->limit_exclusive = quota.exclusive_max; + r = btrfs_subvol_get_subtree_quota_fd(fd, 0, "a); + if (r >= 0) { + (*ret)->usage = quota.referenced; + (*ret)->usage_exclusive = quota.exclusive; + + (*ret)->limit = quota.referenced_max; + (*ret)->limit_exclusive = quota.exclusive_max; + } } return 1; @@ -397,7 +400,7 @@ int image_remove(Image *i) { switch (i->type) { case IMAGE_SUBVOLUME: - r = btrfs_subvol_remove(i->path, true); + r = btrfs_subvol_remove(i->path, BTRFS_REMOVE_RECURSIVE|BTRFS_REMOVE_QUOTA); if (r < 0) return r; break; @@ -587,7 +590,12 @@ int image_clone(Image *i, const char *new_name, bool read_only) { case IMAGE_DIRECTORY: new_path = strjoina("/var/lib/machines/", new_name); - r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE); + r = btrfs_subvol_snapshot(i->path, new_path, (read_only ? BTRFS_SNAPSHOT_READ_ONLY : 0) | BTRFS_SNAPSHOT_FALLBACK_COPY | BTRFS_SNAPSHOT_RECURSIVE | BTRFS_SNAPSHOT_QUOTA); + + /* Enable "subtree" quotas for the copy, if we didn't + * copy any quota from the source. */ + (void) btrfs_subvol_auto_qgroup(i->path, 0, true); + break; case IMAGE_RAW: @@ -629,6 +637,10 @@ int image_read_only(Image *i, bool b) { switch (i->type) { case IMAGE_SUBVOLUME: + + /* Note that we set the flag only on the top-level + * subvolume of the image. */ + r = btrfs_subvol_set_read_only(i->path, b); if (r < 0) return r; @@ -729,7 +741,14 @@ int image_set_limit(Image *i, uint64_t referenced_max) { if (i->type != IMAGE_SUBVOLUME) return -EOPNOTSUPP; - return btrfs_quota_limit(i->path, referenced_max); + /* We set the quota both for the subvolume as well as for the + * subtree. The latter is mostly for historical reasons, since + * we didn't use to have a concept of subtree quota, and hence + * only modified the subvolume quota. */ + + (void) btrfs_qgroup_set_limit(i->path, 0, referenced_max); + (void) btrfs_subvol_auto_qgroup(i->path, 0, true); + return btrfs_subvol_set_subtree_quota_limit(i->path, 0, referenced_max); } int image_name_lock(const char *name, int operation, LockFile *ret) { diff --git a/src/shared/machine-pool.c b/src/shared/machine-pool.c index 8af78f47d5..1da7d0815f 100644 --- a/src/shared/machine-pool.c +++ b/src/shared/machine-pool.c @@ -170,7 +170,7 @@ int setup_machine_directory(uint64_t size, sd_bus_error *error) { }; _cleanup_close_ int fd = -1, control = -1, loop = -1; _cleanup_free_ char* loopdev = NULL; - char tmpdir[] = "/tmp/import-mount.XXXXXX", *mntdir = NULL; + char tmpdir[] = "/tmp/machine-pool.XXXXXX", *mntdir = NULL; bool tmpdir_made = false, mntdir_made = false, mntdir_mounted = false; char buf[FORMAT_BYTES_MAX]; int r, nr = -1; @@ -194,14 +194,35 @@ int setup_machine_directory(uint64_t size, sd_bus_error *error) { r = btrfs_quota_enable("/var/lib/machines", true); if (r < 0) - log_warning_errno(r, "Failed to enable quota, ignoring: %m"); + log_warning_errno(r, "Failed to enable quota for /var/lib/machines, ignoring: %m"); + r = btrfs_subvol_auto_qgroup("/var/lib/machines", 0, true); + if (r < 0) + log_warning_errno(r, "Failed to set up default quota hierarchy for /var/lib/machines, ignoring: %m"); + + return 1; + } + + if (path_is_mount_point("/var/lib/machines", AT_SYMLINK_FOLLOW) > 0) { + log_debug("/var/lib/machines is already a mount point, not creating loopback file for it."); return 0; } - if (path_is_mount_point("/var/lib/machines", AT_SYMLINK_FOLLOW) > 0 || - dir_is_empty("/var/lib/machines") == 0) - return sd_bus_error_setf(error, SD_BUS_ERROR_INVALID_ARGS, "/var/lib/machines is not a btrfs file system. Operation is not supported on legacy file systems."); + r = dir_is_populated("/var/lib/machines"); + if (r < 0 && r != -ENOENT) + return r; + if (r > 0) { + log_debug("/var/log/machines is already populated, not creating loopback file for it."); + return 0; + } + + r = mkfs_exists("btrfs"); + if (r == -ENOENT) { + log_debug("mkfs.btrfs is missing, cannot create loopback file for /var/lib/machines."); + return 0; + } + if (r < 0) + return r; fd = setup_machine_raw(size, error); if (fd < 0) @@ -266,6 +287,10 @@ int setup_machine_directory(uint64_t size, sd_bus_error *error) { if (r < 0) log_warning_errno(r, "Failed to enable quota, ignoring: %m"); + r = btrfs_subvol_auto_qgroup(mntdir, 0, true); + if (r < 0) + log_warning_errno(r, "Failed to set up default quota hierarchy, ignoring: %m"); + if (chmod(mntdir, 0700) < 0) { r = sd_bus_error_set_errnof(error, errno, "Failed to fix owner: %m"); goto fail; @@ -286,7 +311,7 @@ int setup_machine_directory(uint64_t size, sd_bus_error *error) { (void) rmdir(mntdir); (void) rmdir(tmpdir); - return 0; + return 1; fail: if (mntdir_mounted) @@ -370,9 +395,11 @@ int grow_machine_directory(void) { if (r <= 0) return r; - r = btrfs_quota_limit("/var/lib/machines", new_size); - if (r < 0) - return r; + /* Also bump the quota, of both the subvolume leaf qgroup, as + * well as of any subtree quota group by the same id but a + * higher level, if it exists. */ + (void) btrfs_qgroup_set_limit("/var/lib/machines", 0, new_size); + (void) btrfs_subvol_set_subtree_quota_limit("/var/lib/machines", 0, new_size); log_info("Grew /var/lib/machines btrfs loopback file system to %s.", format_bytes(buf, sizeof(buf), new_size)); return 1; diff --git a/src/systemctl/systemctl.c b/src/systemctl/systemctl.c index 420a246be1..b99c64a75a 100644 --- a/src/systemctl/systemctl.c +++ b/src/systemctl/systemctl.c @@ -7399,12 +7399,12 @@ static int talk_initctl(void) { static int systemctl_main(int argc, char *argv[]) { static const Verb verbs[] = { - { "list-units", VERB_ANY, 1, VERB_DEFAULT, list_units }, - { "list-unit-files", VERB_ANY, 1, 0, list_unit_files }, - { "list-sockets", VERB_ANY, 1, 0, list_sockets }, - { "list-timers", VERB_ANY, 1, 0, list_timers }, - { "list-jobs", VERB_ANY, 1, 0, list_jobs }, - { "list-machines", VERB_ANY, 1, 0, list_machines }, + { "list-units", VERB_ANY, VERB_ANY, VERB_DEFAULT, list_units }, + { "list-unit-files", VERB_ANY, VERB_ANY, 0, list_unit_files }, + { "list-sockets", VERB_ANY, VERB_ANY, 0, list_sockets }, + { "list-timers", VERB_ANY, VERB_ANY, 0, list_timers }, + { "list-jobs", VERB_ANY, VERB_ANY, 0, list_jobs }, + { "list-machines", VERB_ANY, VERB_ANY, 0, list_machines }, { "clear-jobs", VERB_ANY, 1, 0, daemon_reload }, { "cancel", 2, VERB_ANY, 0, cancel_job }, { "start", 2, VERB_ANY, 0, start_unit }, diff --git a/src/test/test-btrfs.c b/src/test/test-btrfs.c index e4771c9dd7..60d1258a9b 100644 --- a/src/test/test-btrfs.c +++ b/src/test/test-btrfs.c @@ -27,17 +27,17 @@ #include "btrfs-util.h" int main(int argc, char *argv[]) { + BtrfsQuotaInfo quota; int r, fd; fd = open("/", O_RDONLY|O_CLOEXEC|O_DIRECTORY); if (fd < 0) log_error_errno(errno, "Failed to open root directory: %m"); else { - BtrfsSubvolInfo info; - BtrfsQuotaInfo quota; char ts[FORMAT_TIMESTAMP_MAX], bs[FORMAT_BYTES_MAX]; + BtrfsSubvolInfo info; - r = btrfs_subvol_get_info_fd(fd, &info); + r = btrfs_subvol_get_info_fd(fd, 0, &info); if (r < 0) log_error_errno(r, "Failed to get subvolume info: %m"); else { @@ -45,7 +45,7 @@ int main(int argc, char *argv[]) { log_info("read-only (search): %s", yes_no(info.read_only)); } - r = btrfs_subvol_get_quota_fd(fd, "a); + r = btrfs_qgroup_get_quota_fd(fd, 0, "a); if (r < 0) log_error_errno(r, "Failed to get quota info: %m"); else { @@ -80,15 +80,15 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to make snapshot: %m"); - r = btrfs_subvol_remove("/xxxtest", false); + r = btrfs_subvol_remove("/xxxtest", BTRFS_REMOVE_QUOTA); if (r < 0) log_error_errno(r, "Failed to remove subvolume: %m"); - r = btrfs_subvol_remove("/xxxtest2", false); + r = btrfs_subvol_remove("/xxxtest2", BTRFS_REMOVE_QUOTA); if (r < 0) log_error_errno(r, "Failed to remove subvolume: %m"); - r = btrfs_subvol_remove("/xxxtest3", false); + r = btrfs_subvol_remove("/xxxtest3", BTRFS_REMOVE_QUOTA); if (r < 0) log_error_errno(r, "Failed to remove subvolume: %m"); @@ -96,7 +96,7 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to make snapshot: %m"); - r = btrfs_subvol_remove("/etc2", false); + r = btrfs_subvol_remove("/etc2", BTRFS_REMOVE_QUOTA); if (r < 0) log_error_errno(r, "Failed to remove subvolume: %m"); @@ -137,13 +137,61 @@ int main(int argc, char *argv[]) { if (r < 0) log_error_errno(r, "Failed to snapshot subvolume: %m"); - r = btrfs_subvol_remove("/xxxrectest", true); + r = btrfs_subvol_remove("/xxxrectest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); if (r < 0) log_error_errno(r, "Failed to recursively remove subvolume: %m"); - r = btrfs_subvol_remove("/xxxrectest2", true); + r = btrfs_subvol_remove("/xxxrectest2", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); if (r < 0) log_error_errno(r, "Failed to recursively remove subvolume: %m"); + r = btrfs_subvol_make("/xxxquotatest"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + r = btrfs_subvol_auto_qgroup("/xxxquotatest", 0, true); + if (r < 0) + log_error_errno(r, "Failed to set up auto qgroup: %m"); + + r = btrfs_subvol_make("/xxxquotatest/beneath"); + if (r < 0) + log_error_errno(r, "Failed to make subvolume: %m"); + + r = btrfs_subvol_auto_qgroup("/xxxquotatest/beneath", 0, false); + if (r < 0) + log_error_errno(r, "Failed to set up auto qgroup: %m"); + + r = btrfs_qgroup_set_limit("/xxxquotatest/beneath", 0, 4ULL * 1024 * 1024 * 1024); + if (r < 0) + log_error_errno(r, "Failed to set up quota limit: %m"); + + r = btrfs_subvol_set_subtree_quota_limit("/xxxquotatest", 0, 5ULL * 1024 * 1024 * 1024); + if (r < 0) + log_error_errno(r, "Failed to set up quota limit: %m"); + + r = btrfs_subvol_snapshot("/xxxquotatest", "/xxxquotatest2", BTRFS_SNAPSHOT_RECURSIVE|BTRFS_SNAPSHOT_QUOTA); + if (r < 0) + log_error_errno(r, "Failed to setup snapshot: %m"); + + r = btrfs_qgroup_get_quota("/xxxquotatest2/beneath", 0, "a); + if (r < 0) + log_error_errno(r, "Failed to query quota: %m"); + + assert_se(quota.referenced_max == 4ULL * 1024 * 1024 * 1024); + + r = btrfs_subvol_get_subtree_quota("/xxxquotatest2", 0, "a); + if (r < 0) + log_error_errno(r, "Failed to query quota: %m"); + + assert_se(quota.referenced_max == 5ULL * 1024 * 1024 * 1024); + + r = btrfs_subvol_remove("/xxxquotatest", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); + if (r < 0) + log_error_errno(r, "Failed remove subvolume: %m"); + + r = btrfs_subvol_remove("/xxxquotatest2", BTRFS_REMOVE_QUOTA|BTRFS_REMOVE_RECURSIVE); + if (r < 0) + log_error_errno(r, "Failed remove subvolume: %m"); + return 0; } diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index d219764bc6..f636a4d33b 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -69,6 +69,8 @@ typedef enum ItemType { CREATE_DIRECTORY = 'd', TRUNCATE_DIRECTORY = 'D', CREATE_SUBVOLUME = 'v', + CREATE_SUBVOLUME_INHERIT_QUOTA = 'q', + CREATE_SUBVOLUME_NEW_QUOTA = 'Q', CREATE_FIFO = 'p', CREATE_SYMLINK = 'L', CREATE_CHAR_DEVICE = 'c', @@ -180,6 +182,8 @@ static bool takes_ownership(ItemType t) { CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, + CREATE_SUBVOLUME_INHERIT_QUOTA, + CREATE_SUBVOLUME_NEW_QUOTA, CREATE_FIFO, CREATE_SYMLINK, CREATE_CHAR_DEVICE, @@ -1198,16 +1202,16 @@ static int create_item(Item *i) { case CREATE_DIRECTORY: case TRUNCATE_DIRECTORY: case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: RUN_WITH_UMASK(0000) mkdir_parents_label(i->path, 0755); - if (i->type == CREATE_SUBVOLUME) - RUN_WITH_UMASK((~i->mode) & 0777) { + if (IN_SET(i->type, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) { + RUN_WITH_UMASK((~i->mode) & 0777) r = btrfs_subvol_make(i->path); - log_debug_errno(r, "Creating subvolume \"%s\": %m", i->path); - } - else + } else r = 0; if (IN_SET(i->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY) || r == -ENOTTY) @@ -1236,6 +1240,24 @@ static int create_item(Item *i) { log_debug("%s directory \"%s\".", creation_mode_verb_to_string(creation), i->path); + if (IN_SET(i->type, CREATE_SUBVOLUME_NEW_QUOTA, CREATE_SUBVOLUME_INHERIT_QUOTA)) { + r = btrfs_subvol_auto_qgroup(i->path, 0, i->type == CREATE_SUBVOLUME_NEW_QUOTA); + if (r == -ENOTTY) { + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because of unsupported file system or because directory is not a subvolume: %m", i->path); + return 0; + } + if (r == -EROFS) { + log_debug_errno(r, "Couldn't adjust quota for subvolume \"%s\" because of read-only file system: %m", i->path); + return 0; + } + if (r < 0) + return log_error_errno(r, "Failed to adjust quota for subvolume \"%s\": %m", i->path); + if (r > 0) + log_debug("Adjusted quota for subvolume \"%s\".", i->path); + if (r == 0) + log_debug("Quota for subvolume \"%s\" already in place, no change made.", i->path); + } + r = path_set_perms(i, i->path); if (r < 0) return r; @@ -1492,6 +1514,8 @@ static int remove_item(Item *i) { case TRUNCATE_FILE: case CREATE_DIRECTORY: case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: case CREATE_FIFO: case CREATE_SYMLINK: case CREATE_CHAR_DEVICE: @@ -1583,6 +1607,8 @@ static int clean_item(Item *i) { switch (i->type) { case CREATE_DIRECTORY: case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: case TRUNCATE_DIRECTORY: case IGNORE_PATH: case COPY_FILES: @@ -1819,6 +1845,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { case CREATE_DIRECTORY: case CREATE_SUBVOLUME: + case CREATE_SUBVOLUME_INHERIT_QUOTA: + case CREATE_SUBVOLUME_NEW_QUOTA: case TRUNCATE_DIRECTORY: case CREATE_FIFO: case IGNORE_PATH: @@ -1983,8 +2011,7 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { i.mode = m; i.mode_set = true; } else - i.mode = IN_SET(i.type, CREATE_DIRECTORY, CREATE_SUBVOLUME, TRUNCATE_DIRECTORY) - ? 0755 : 0644; + i.mode = IN_SET(i.type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA) ? 0755 : 0644; if (!isempty(age) && !streq(age, "-")) { const char *a = age; @@ -2186,7 +2213,7 @@ static int read_config_file(const char *fn, bool ignore_enoent) { continue; ORDERED_HASHMAP_FOREACH(j, items, iter) { - if (j->type != CREATE_DIRECTORY && j->type != TRUNCATE_DIRECTORY && j->type != CREATE_SUBVOLUME) + if (!IN_SET(j->type, CREATE_DIRECTORY, TRUNCATE_DIRECTORY, CREATE_SUBVOLUME, CREATE_SUBVOLUME_INHERIT_QUOTA, CREATE_SUBVOLUME_NEW_QUOTA)) continue; if (path_equal(j->path, i->path)) { diff --git a/tmpfiles.d/home.conf b/tmpfiles.d/home.conf index aa652b197f..9f25b83392 100644 --- a/tmpfiles.d/home.conf +++ b/tmpfiles.d/home.conf @@ -7,5 +7,5 @@ # See tmpfiles.d(5) for details -v /home 0755 - - - -v /srv 0755 - - - +Q /home 0755 - - - +q /srv 0755 - - - diff --git a/tmpfiles.d/systemd-nspawn.conf b/tmpfiles.d/systemd-nspawn.conf index 5a3124a0fc..9fa3878d6b 100644 --- a/tmpfiles.d/systemd-nspawn.conf +++ b/tmpfiles.d/systemd-nspawn.conf @@ -7,7 +7,7 @@ # See tmpfiles.d(5) for details -v /var/lib/machines 0700 - - - +Q /var/lib/machines 0700 - - - # Remove old temporary snapshots, but only at boot. Ideally we'd have # "self-destroying" btrfs snapshots that go away if the last last diff --git a/tmpfiles.d/tmp.conf b/tmpfiles.d/tmp.conf index ffdd82fd9c..6bbd1aa341 100644 --- a/tmpfiles.d/tmp.conf +++ b/tmpfiles.d/tmp.conf @@ -8,8 +8,8 @@ # See tmpfiles.d(5) for details # Clear tmp directories separately, to make them easier to override -v /tmp 1777 root root 10d -v /var/tmp 1777 root root 30d +q /tmp 1777 root root 10d +q /var/tmp 1777 root root 30d # Exclude namespace mountpoints created with PrivateTmp=yes x /tmp/systemd-private-%b-* diff --git a/tmpfiles.d/var.conf b/tmpfiles.d/var.conf index 472680c3bf..ae7952e77a 100644 --- a/tmpfiles.d/var.conf +++ b/tmpfiles.d/var.conf @@ -7,7 +7,7 @@ # See tmpfiles.d(5) for details -v /var 0755 - - - +q /var 0755 - - - L /var/run - - - - ../run diff --git a/units/systemd-nspawn@.service.in b/units/systemd-nspawn@.service.in index 03349931d9..2e79adff44 100644 --- a/units/systemd-nspawn@.service.in +++ b/units/systemd-nspawn@.service.in @@ -39,6 +39,7 @@ DeviceAllow=char-pts rw # implement the --image= option. Add these here, too. DeviceAllow=/dev/loop-control rw DeviceAllow=block-loop rw +DeviceAllow=block-blkext rw [Install] WantedBy=machines.target |