pid1: add GracefulOptions= setting to .mount units (#36023)

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.
This commit is contained in:
Lennart Poettering
2025-01-16 07:47:08 +01:00
committed by GitHub
10 changed files with 161 additions and 9 deletions

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

View File

@@ -650,6 +650,22 @@
<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" />

View 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;
}

View 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') }}

View 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;

View 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);

View 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)

View 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;

View 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)

View File

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