tmpfiles: introduce "q" and "Q" for creating quota-enabled btrfs subvolumes
authorLennart Poettering <lennart@poettering.net>
Wed, 21 Oct 2015 17:46:23 +0000 (19:46 +0200)
committerLennart Poettering <lennart@poettering.net>
Wed, 21 Oct 2015 23:59:25 +0000 (01:59 +0200)
This allows us to set up the quota group hierarchy in a reasonable way
on btrfs file systems.

man/tmpfiles.d.xml
src/tmpfiles/tmpfiles.c

index 3cee0ff..662ec45 100644 (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.
           <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>
       <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>
 
index d219764..f636a4d 100644 (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)) {