tmpfiles: introduce "q" and "Q" for creating quota-enabled btrfs subvolumes

This allows us to set up the quota group hierarchy in a reasonable way
on btrfs file systems.
This commit is contained in:
Lennart Poettering 2015-10-21 19:46:23 +02:00
parent 8c9cfc2844
commit 5fb13eb51b
2 changed files with 116 additions and 19 deletions

View File

@ -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>

View File

@ -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)) {