diff --git a/TODO b/TODO
index 96d28f8bf0..d6d9901e64 100644
--- a/TODO
+++ b/TODO
@@ -24,9 +24,6 @@ Janitorial Clean-ups:
Features:
-* document specifiers systemd-sysusers understands in its man page (the way
- it's already documented in tmpfiles.d(5)' man page)
-
* add O_TMPFILE support to copy_file_atomic()
* nspawn: greater control over selinux label?
diff --git a/man/sysusers.d.xml b/man/sysusers.d.xml
index 71f6926ae8..534f008757 100644
--- a/man/sysusers.d.xml
+++ b/man/sysusers.d.xml
@@ -229,6 +229,66 @@ u root 0 "Superuser" /root /bin/zsh
+
+ Specifiers
+
+ Specifiers can be used in the "Name", "ID", "GECOS", "Home directory", and "Shell" fields.
+ An unknown or unresolvable specifier is treated as invalid configuration.
+ The following expansions are understood:
+
+ Specifiers available
+
+
+
+
+
+
+ Specifier
+ Meaning
+ Details
+
+
+
+
+ %b
+ Boot ID
+ The boot ID of the running system, formatted as string. See random4 for more information.
+
+
+ %H
+ Host name
+ The hostname of the running system.
+
+
+ %m
+ Machine ID
+ The machine ID of the running system, formatted as string. See machine-id5 for more information.
+
+
+ %T
+ Directory for temporary files
+ This is either /tmp or the path $TMPDIR, $TEMP or $TMP are set to.
+
+
+ %v
+ Kernel release
+ Identical to uname -r output.
+
+
+ %V
+ Directory for larger and persistent temporary files
+ This is either /var/tmp or the path $TMPDIR, $TEMP or $TMP are set to.
+
+
+ %%
+ Escaped %
+ Single percent sign.
+
+
+
+
+
+
Idempotence
diff --git a/src/sysusers/sysusers.c b/src/sysusers/sysusers.c
index b521200346..c14cfd48af 100644
--- a/src/sysusers/sysusers.c
+++ b/src/sysusers/sysusers.c
@@ -1371,8 +1371,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
_cleanup_free_ char *action = NULL,
*name = NULL, *resolved_name = NULL,
*id = NULL, *resolved_id = NULL,
- *description = NULL,
- *home = NULL,
+ *description = NULL, *resolved_description = NULL,
+ *home = NULL, *resolved_home = NULL,
*shell, *resolved_shell = NULL;
_cleanup_(item_freep) Item *i = NULL;
Item *existing;
@@ -1446,8 +1446,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
description = mfree(description);
if (description) {
- if (!valid_gecos(description)) {
- log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, description);
+ r = specifier_printf(description, specifier_table, NULL, &resolved_description);
+ if (r < 0) {
+ log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, description);
+ return r;
+ }
+
+ if (!valid_gecos(resolved_description)) {
+ log_error("[%s:%u] '%s' is not a valid GECOS field.", fname, line, resolved_description);
return -EINVAL;
}
}
@@ -1457,8 +1463,14 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
home = mfree(home);
if (home) {
- if (!valid_home(home)) {
- log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, home);
+ r = specifier_printf(home, specifier_table, NULL, &resolved_home);
+ if (r < 0) {
+ log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, home);
+ return r;
+ }
+
+ if (!valid_home(resolved_home)) {
+ log_error("[%s:%u] '%s' is not a valid home directory field.", fname, line, resolved_home);
return -EINVAL;
}
}
@@ -1608,8 +1620,8 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) {
}
}
- i->description = TAKE_PTR(description);
- i->home = TAKE_PTR(home);
+ i->description = TAKE_PTR(resolved_description);
+ i->home = TAKE_PTR(resolved_home);
i->shell = TAKE_PTR(resolved_shell);
h = users;