summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--TODO5
-rw-r--r--man/journalctl.xml4
-rw-r--r--man/systemd-nspawn.xml2
-rw-r--r--man/tmpfiles.d.xml92
-rw-r--r--src/basic/btrfs-util.c905
-rw-r--r--src/basic/btrfs-util.h73
-rw-r--r--src/basic/missing.h4
-rw-r--r--src/basic/path-util.c23
-rw-r--r--src/basic/path-util.h1
-rw-r--r--src/basic/rm-rf.c4
-rw-r--r--src/basic/util.c17
-rw-r--r--src/basic/util.h8
-rw-r--r--src/core/dbus-execute.c9
-rw-r--r--src/import/export-tar.c4
-rw-r--r--src/import/import-tar.c2
-rw-r--r--src/import/pull-common.c5
-rw-r--r--src/import/pull-dkr.c16
-rw-r--r--src/import/pull-raw.c6
-rw-r--r--src/import/pull-tar.c8
-rw-r--r--src/journal/journalctl.c2
-rw-r--r--src/login/logind-session.c16
-rw-r--r--src/machine/machinectl.c2
-rw-r--r--src/machine/machined-dbus.c8
-rw-r--r--src/nspawn/nspawn-settings.c20
-rw-r--r--src/nspawn/nspawn-settings.h3
-rw-r--r--src/nspawn/nspawn.c34
-rw-r--r--src/shared/import-util.c27
-rw-r--r--src/shared/import-util.h2
-rw-r--r--src/shared/machine-image.c41
-rw-r--r--src/shared/machine-pool.c45
-rw-r--r--src/systemctl/systemctl.c12
-rw-r--r--src/test/test-btrfs.c68
-rw-r--r--src/tmpfiles/tmpfiles.c43
-rw-r--r--tmpfiles.d/home.conf4
-rw-r--r--tmpfiles.d/systemd-nspawn.conf2
-rw-r--r--tmpfiles.d/tmp.conf4
-rw-r--r--tmpfiles.d/var.conf2
-rw-r--r--units/systemd-nspawn@.service.in1
38 files changed, 1322 insertions, 202 deletions
diff --git a/TODO b/TODO
index c466570278..c404be8cce 100644
--- a/TODO
+++ b/TODO
@@ -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/journalctl.xml b/man/journalctl.xml
index 91a5536ade..9784b9f2a8 100644
--- a/man/journalctl.xml
+++ b/man/journalctl.xml
@@ -368,7 +368,9 @@
<term><option>-q</option></term>
<term><option>--quiet</option></term>
- <listitem><para>Suppresses any warning messages regarding
+ <listitem><para>Suppresses all info messages
+ (i.e. "-- Logs begin at ...", "-- Reboot --"),
+ any warning messages regarding
inaccessible system journals when run as a normal
user.</para></listitem>
</varlistentry>
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/journal/journalctl.c b/src/journal/journalctl.c
index 5dafb0bcab..863c15b738 100644
--- a/src/journal/journalctl.c
+++ b/src/journal/journalctl.c
@@ -218,7 +218,7 @@ static void help(void) {
" -x --catalog Add message explanations where available\n"
" --no-full Ellipsize fields\n"
" -a --all Show all fields, including long and unprintable\n"
- " -q --quiet Do not show privilege warning\n"
+ " -q --quiet Do not show info messages and privilege warning\n"
" --no-pager Do not pipe output into a pager\n"
" -m --merge Show entries from all available journals\n"
" -D --directory=PATH Show journal files from directory\n"
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, &quota);
- 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, &quota);
+ 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, &quota);
+ r = btrfs_qgroup_get_quota_fd(fd, 0, &quota);
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, &quota);
+ 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, &quota);
+ 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