]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
pid1: add GracefulOptions= setting to .mount units 36023/head
authorLennart Poettering <lennart@poettering.net>
Tue, 14 Jan 2025 15:49:52 +0000 (16:49 +0100)
committerLennart Poettering <lennart@poettering.net>
Wed, 15 Jan 2025 20:05:06 +0000 (21:05 +0100)
This new setting can be used to specify mount options that shall only be
added to the mount option string if the kernel supports them.

This shall be used for adding "usrquota" to tmp.mount without breaking compat,
but is generally be useful.

man/org.freedesktop.systemd1.xml
man/systemd.mount.xml
src/core/dbus-mount.c
src/core/load-fragment-gperf.gperf.in
src/core/load-fragment.c
src/core/load-fragment.h
src/core/mount.c
src/core/mount.h
src/shared/bus-unit-util.c
test/units/TEST-74-AUX-UTILS.mount.sh

index 1fc925fe4a72f136ac179f63fcba42fb6d276170..0664d026f1456aa02b70abb52449654dc7a9b686 100644 (file)
@@ -7050,6 +7050,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
       readonly s Result = '...';
       readonly u UID = ...;
       readonly u GID = ...;
+      @org.freedesktop.DBus.Property.EmitsChangedSignal("const")
+      readonly as GracefulOptions = ['...', ...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
       readonly a(sasbttttuii) ExecMount = [...];
       @org.freedesktop.DBus.Property.EmitsChangedSignal("invalidates")
@@ -7654,6 +7656,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <!--property GID is not documented!-->
 
+    <!--property GracefulOptions is not documented!-->
+
     <!--property ExecUnmount is not documented!-->
 
     <!--property ExecRemount is not documented!-->
@@ -8200,6 +8204,8 @@ node /org/freedesktop/systemd1/unit/home_2emount {
 
     <variablelist class="dbus-property" generated="True" extra-ref="GID"/>
 
+    <variablelist class="dbus-property" generated="True" extra-ref="GracefulOptions"/>
+
     <variablelist class="dbus-property" generated="True" extra-ref="ExecMount"/>
 
     <variablelist class="dbus-property" generated="True" extra-ref="ExecUnmount"/>
@@ -12455,7 +12461,9 @@ $ gdbus introspect --system --dest org.freedesktop.systemd1 \
       <varname>ManagedOOMMemoryPressureDurationUSec</varname>,
       <varname>ProtectControlGroupsEx</varname>, and
       <varname>PrivatePIDs</varname> were added in version 257.</para>
-      <para><varname>ProtectHostnameEx</varname> and <function>RemoveSubgroup()</function> was added in version 258.</para>
+      <para><varname>ProtectHostnameEx</varname>,
+      <function>RemoveSubgroup()</function>, and
+      <varname>GracefulOptions</varname> were added in version 258.</para>
     </refsect2>
     <refsect2>
       <title>Swap Unit Objects</title>
index 3043e904995b89b6f07774025997a97081ba45e1..991c1f8506f19eccce1466b23275aeac400f578d 100644 (file)
         <citerefentry><refentrytitle>systemd-system.conf</refentrytitle><manvolnum>5</manvolnum></citerefentry>.
         </para></listitem>
       </varlistentry>
+
+      <varlistentry>
+        <term><varname>GracefulOptions=</varname></term>
+
+        <listitem><para>Additional mount options that shall be appended to <varname>Options=</varname> if
+        supported by the kernel. This may be used to configure mount options that are optional and only
+        enabled on kernels that support them. Note that this is supported only for native kernel mount
+        options (i.e. explicitly not for mount options implemented in userspace, such as those processed by
+        <command>/usr/bin/mount</command> itself, by FUSE or by mount helpers such as
+        <command>mount.nfs</command>).</para>
+
+        <para>May be specified multiple times. If specified multiple times, all listed, supported mount
+        options are combined. If an empty string is assigned, the list is reset.</para>
+
+        <xi:include href="version-info.xml" xpointer="v258"/></listitem>
+      </varlistentry>
     </variablelist>
 
     <xi:include href="systemd.service.xml" xpointer="shared-unit-options" />
index d59aa06a11d286cf0e2677eac70a9a23c0f0cda2..855300d0254660068dfa36f4cc9b807ddcea3581 100644 (file)
@@ -75,6 +75,7 @@ const sd_bus_vtable bus_mount_vtable[] = {
         SD_BUS_PROPERTY("Result", "s", property_get_result, offsetof(Mount, result), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("UID", "u", bus_property_get_uid, offsetof(Unit, ref_uid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
         SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE),
+        SD_BUS_PROPERTY("GracefulOptions", "as", NULL, offsetof(Mount, graceful_options), SD_BUS_VTABLE_PROPERTY_CONST),
         BUS_EXEC_COMMAND_VTABLE("ExecMount", offsetof(Mount, exec_command[MOUNT_EXEC_MOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         BUS_EXEC_COMMAND_VTABLE("ExecUnmount", offsetof(Mount, exec_command[MOUNT_EXEC_UNMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
         BUS_EXEC_COMMAND_VTABLE("ExecRemount", offsetof(Mount, exec_command[MOUNT_EXEC_REMOUNT]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION),
@@ -150,6 +151,30 @@ static int bus_mount_set_transient_property(
         if (streq(name, "ReadWriteOnly"))
                 return bus_set_transient_bool(u, name, &m->read_write_only, message, flags, error);
 
+        if (streq(name, "GracefulOptions")) {
+                _cleanup_strv_free_ char **add = NULL;
+                r = sd_bus_message_read_strv(message, &add);
+                if (r < 0)
+                        return r;
+
+                if (!UNIT_WRITE_FLAGS_NOOP(flags)) {
+
+                        if (strv_isempty(add)) {
+                                m->graceful_options = strv_free(m->graceful_options);
+                                unit_write_settingf(u, flags, name, "GracefulOptions=");
+                        } else {
+                                r = strv_extend_strv(&m->graceful_options, add, /* filter_duplicates= */ false);
+                                if (r < 0)
+                                        return r;
+
+                                STRV_FOREACH(a, add)
+                                        unit_write_settingf(u, flags|UNIT_ESCAPE_SPECIFIERS, name, "GracefulOptions=%s", *a);
+                        }
+                }
+
+                return 1;
+        }
+
         return 0;
 }
 
index 04a560110e60105b83d3ed2cc45fe75baa4cb7f6..1033d08103e552136e6efbab02ace27191c8fd0e 100644 (file)
@@ -548,6 +548,7 @@ Mount.SloppyOptions,                          config_parse_bool,
 Mount.LazyUnmount,                            config_parse_bool,                                  0,                                  offsetof(Mount, lazy_unmount)
 Mount.ForceUnmount,                           config_parse_bool,                                  0,                                  offsetof(Mount, force_unmount)
 Mount.ReadWriteOnly,                          config_parse_bool,                                  0,                                  offsetof(Mount, read_write_only)
+Mount.GracefulOptions,                        config_parse_mount_graceful_options,                0,                                  offsetof(Mount, graceful_options)
 {{ EXEC_CONTEXT_CONFIG_ITEMS('Mount') }}
 {{ CGROUP_CONTEXT_CONFIG_ITEMS('Mount') }}
 {{ KILL_CONTEXT_CONFIG_ITEMS('Mount') }}
index c8fff60108253088c9d368ba6aa3be06523d8659..fe60f2725975d9b2672d3e160906c78edfa92dae 100644 (file)
@@ -6123,6 +6123,51 @@ int config_parse_mount_node(
         return config_parse_string(unit, filename, line, section, section_line, lvalue, ltype, path, data, userdata);
 }
 
+int config_parse_mount_graceful_options(
+                const char *unit,
+                const char *filename,
+                unsigned line,
+                const char *section,
+                unsigned section_line,
+                const char *lvalue,
+                int ltype,
+                const char *rvalue,
+                void *data,
+                void *userdata) {
+
+        const Unit *u = ASSERT_PTR(userdata);
+        char ***sv = ASSERT_PTR(data);
+        int r;
+
+        assert(filename);
+        assert(lvalue);
+        assert(rvalue);
+
+        if (isempty(rvalue)) {
+                *sv = strv_free(*sv);
+                return 1;
+        }
+
+        _cleanup_free_ char *resolved = NULL;
+        r = unit_full_printf(u, rvalue, &resolved);
+        if (r < 0) {
+                log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to resolve unit specifiers in '%s', ignoring: %m", rvalue);
+                return 0;
+        }
+
+        _cleanup_strv_free_ char **strv = NULL;
+
+        r = strv_split_full(&strv, resolved, ",", EXTRACT_RETAIN_ESCAPE|EXTRACT_UNESCAPE_SEPARATORS);
+        if (r < 0)
+                return log_syntax_parse_error(unit, filename, line, r, lvalue, rvalue);
+
+        r = strv_extend_strv_consume(sv, TAKE_PTR(strv), /* filter_duplicates = */ false);
+        if (r < 0)
+                return log_oom();
+
+        return 1;
+}
+
 static int merge_by_names(Unit *u, Set *names, const char *id) {
         char *k;
         int r;
index 881ce152d550b00d1845abd72c62e5d9551172f4..28275af8d8e869c0f9e83002b807c0bd2529c893 100644 (file)
@@ -165,6 +165,7 @@ CONFIG_PARSER_PROTOTYPE(config_parse_open_file);
 CONFIG_PARSER_PROTOTYPE(config_parse_memory_pressure_watch);
 CONFIG_PARSER_PROTOTYPE(config_parse_cgroup_nft_set);
 CONFIG_PARSER_PROTOTYPE(config_parse_mount_node);
+CONFIG_PARSER_PROTOTYPE(config_parse_mount_graceful_options);
 
 /* gperf prototypes */
 const struct ConfigPerfItem* load_fragment_gperf_lookup(const char *key, GPERF_LEN_TYPE length);
index 4b47092c1d9c547b0046993be35512f364240da2..073e9c71933a8ac2caf2cb7f84d03e0d956899c0 100644 (file)
@@ -231,6 +231,8 @@ static void mount_done(Unit *u) {
         mount_unwatch_control_pid(m);
 
         m->timer_event_source = sd_event_source_disable_unref(m->timer_event_source);
+
+        m->graceful_options = strv_free(m->graceful_options);
 }
 
 static int update_parameters_proc_self_mountinfo(
@@ -1079,6 +1081,44 @@ fail:
         mount_enter_dead_or_mounted(m, MOUNT_FAILURE_RESOURCES, /* flush_result = */ false);
 }
 
+static int mount_append_graceful_options(Mount *m, const MountParameters *p, char **opts) {
+        int r;
+
+        assert(m);
+        assert(p);
+        assert(opts);
+
+        if (strv_isempty(m->graceful_options))
+                return 0;
+
+        if (!p->fstype) {
+                log_unit_warning(UNIT(m), "GracefulOptions= used but file system type not known, suppressing all graceful options.");
+                return 0;
+        }
+
+        STRV_FOREACH(o, m->graceful_options) {
+                _cleanup_free_ char *k = NULL, *v = NULL;
+
+                r = split_pair(*o, "=", &k, &v);
+                if (r < 0 && r != -EINVAL) /* EINVAL → not a key/value pair */
+                        return r;
+
+                r = mount_option_supported(p->fstype, k ?: *o, v);
+                if (r < 0)
+                        log_unit_warning_errno(UNIT(m), r, "GracefulOptions=%s specified, but cannot determine availability, suppressing.", *o);
+                else if (r == 0)
+                        log_unit_info(UNIT(m), "GracefulOptions=%s specified, but option is not available, suppressing.", *o);
+                else {
+                        log_unit_debug(UNIT(m), "GracefulOptions=%s specified and supported, appending to mount option string.", *o);
+
+                        if (!strextend_with_separator(opts, ",", *o))
+                                return -ENOMEM;
+                }
+        }
+
+        return 0;
+}
+
 static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParameters *p) {
         int r;
 
@@ -1113,6 +1153,10 @@ static int mount_set_mount_command(Mount *m, ExecCommand *c, const MountParamete
         if (r < 0)
                 return r;
 
+        r = mount_append_graceful_options(m, p, &opts);
+        if (r < 0)
+                return r;
+
         if (!isempty(opts)) {
                 r = exec_command_append(c, "-o", opts, NULL);
                 if (r < 0)
index 9a8b6bb4c0a0271e43404fcd066cb7535b0ca178..28cc7785d88e127fd1aaf34cf5af473a55eb74a3 100644 (file)
@@ -88,6 +88,8 @@ struct Mount {
         sd_event_source *timer_event_source;
 
         unsigned n_retry_umount;
+
+        char **graceful_options;
 };
 
 extern const UnitVTable mount_vtable;
index 1b6c6f0566f9d87c62b1c4294057428d02175ce9..ed739503551cac197559fea2eeaf7aa2c2775273 100644 (file)
@@ -147,7 +147,7 @@ static int bus_append_string(sd_bus_message *m, const char *field, const char *e
         return 1;
 }
 
-static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq, ExtractFlags flags) {
+static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq, const char *separator, ExtractFlags flags) {
         const char *p;
         int r;
 
@@ -170,7 +170,7 @@ static int bus_append_strv(sd_bus_message *m, const char *field, const char *eq,
         for (p = eq;;) {
                 _cleanup_free_ char *word = NULL;
 
-                r = extract_first_word(&p, &word, NULL, flags);
+                r = extract_first_word(&p, &word, separator, flags);
                 if (r == 0)
                         break;
                 if (r == -ENOMEM)
@@ -613,12 +613,12 @@ static int bus_append_cgroup_property(sd_bus_message *m, const char *field, cons
                 return bus_append_cg_blkio_weight_parse(m, field, eq);
 
         if (streq(field, "DisableControllers"))
-                return bus_append_strv(m, "DisableControllers", eq, EXTRACT_UNQUOTE);
+                return bus_append_strv(m, "DisableControllers", eq, /* separator= */ NULL, EXTRACT_UNQUOTE);
 
         if (streq(field, "Delegate")) {
                 r = parse_boolean(eq);
                 if (r < 0)
-                        return bus_append_strv(m, "DelegateControllers", eq, EXTRACT_UNQUOTE);
+                        return bus_append_strv(m, "DelegateControllers", eq, /* separator= */ NULL, EXTRACT_UNQUOTE);
 
                 r = sd_bus_message_append(m, "(sv)", "Delegate", "b", r);
                 if (r < 0)
@@ -1116,7 +1116,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
                               "ConfigurationDirectory",
                               "SupplementaryGroups",
                               "SystemCallArchitectures"))
-                return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE);
+                return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE);
 
         if (STR_IN_SET(field, "SyslogLevel",
                               "LogLevelMax"))
@@ -1175,7 +1175,7 @@ static int bus_append_execute_property(sd_bus_message *m, const char *field, con
         if (STR_IN_SET(field, "Environment",
                               "UnsetEnvironment",
                               "PassEnvironment"))
-                return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE);
+                return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE|EXTRACT_CUNESCAPE);
 
         if (streq(field, "EnvironmentFile")) {
                 if (isempty(eq))
@@ -2333,6 +2333,9 @@ static int bus_append_mount_property(sd_bus_message *m, const char *field, const
                               "ReadwriteOnly"))
                 return bus_append_parse_boolean(m, field, eq);
 
+        if (streq(field, "GracefulOptions"))
+                return bus_append_strv(m, field, eq, /* separator= */ ",", 0);
+
         return 0;
 }
 
@@ -2615,7 +2618,7 @@ static int bus_append_socket_property(sd_bus_message *m, const char *field, cons
                 return bus_append_string(m, field, eq);
 
         if (streq(field, "Symlinks"))
-                return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE);
+                return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE);
 
         if (streq(field, "SocketProtocol"))
                 return bus_append_parse_ip_protocol(m, field, eq);
@@ -2749,7 +2752,7 @@ static int bus_append_unit_property(sd_bus_message *m, const char *field, const
                               "RequiresMountsFor",
                               "WantsMountsFor",
                               "Markers"))
-                return bus_append_strv(m, field, eq, EXTRACT_UNQUOTE);
+                return bus_append_strv(m, field, eq, /* separator= */ NULL, EXTRACT_UNQUOTE);
 
         t = condition_type_from_string(field);
         if (t >= 0)
index 89a391d48e1e6e7d9e82ed31688fda2b4f8cbe89..0700b43063f0cb3352fb86e54a41d34a43b3101f 100755 (executable)
@@ -185,3 +185,10 @@ systemctl status "$WORK_DIR/mnt"
 touch "$WORK_DIR/mnt/hello"
 [[ "$(stat -c "%U:%G" "$WORK_DIR/mnt/hello")" == "testuser:testuser" ]]
 systemd-umount LABEL=owner-vfat
+
+# Mkae sure that graceful mount options work
+GRACEFULTEST="/tmp/graceful/$RANDOM"
+systemd-mount --tmpfs -p GracefulOptions=idefinitelydontexist,nr_inodes=4711,idonexisteither "$GRACEFULTEST"
+findmnt -n -o options "$GRACEFULTEST"
+findmnt -n -o options "$GRACEFULTEST" | grep -q nr_inodes=4711
+umount "$GRACEFULTEST"