summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDaan De Meyer <daan.j.demeyer@gmail.com>2024-11-04 12:21:21 +0100
committerDaan De Meyer <daan.j.demeyer@gmail.com>2024-11-04 19:04:21 +0100
commitb5dc805583bbb019c6bf4c73bf0814a396dc0f12 (patch)
tree5c72a94907194ed3bb04377fed4c0569994774bd
parentefi-loader: add missing stub for efi_stub_get_device_part_uuid() (diff)
downloadsystemd-b5dc805583bbb019c6bf4c73bf0814a396dc0f12.tar.xz
systemd-b5dc805583bbb019c6bf4c73bf0814a396dc0f12.zip
tmpfiles: Implement L? to only create symlinks if source exists
This allows a single tmpfiles snippet with lines to symlink directories from /usr/share/factory to be shared across many different configurations while making sure symlinks only get created if the source actually exists.
-rw-r--r--man/tmpfiles.d.xml16
-rw-r--r--src/tmpfiles/tmpfiles.c30
-rwxr-xr-xtest/units/TEST-22-TMPFILES.21.sh20
3 files changed, 56 insertions, 10 deletions
diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml
index a721c1e66d..4e6652dc01 100644
--- a/man/tmpfiles.d.xml
+++ b/man/tmpfiles.d.xml
@@ -299,15 +299,13 @@ L /tmp/foobar - - - - /dev/null</programlisting>
<varlistentry>
<term><varname>L</varname></term>
<term><varname>L+</varname></term>
- <listitem><para>Create a symlink if it does not exist
- yet. If suffixed with <varname>+</varname> and a file or
- directory already exists where the symlink is to be created,
- it will be removed and be replaced by the symlink. If the
- argument is omitted, symlinks to files with the same name
- residing in the directory
- <filename>/usr/share/factory/</filename> are created. Note
- that permissions on symlinks are ignored.
- </para></listitem>
+ <term><varname>L?</varname></term>
+ <listitem><para>Create a symlink if it does not exist yet. If suffixed with <varname>+</varname>
+ and a file or directory already exists where the symlink is to be created, it will be removed and
+ be replaced by the symlink. If suffixed with <varname>?</varname> and the source path does not
+ exist, the symlink is not created. If the argument is omitted, symlinks to files with the same name
+ residing in the directory <filename>/usr/share/factory/</filename> are created. Note that
+ permissions on symlinks are ignored.</para></listitem>
</varlistentry>
<varlistentry>
diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c
index c4e032d8d7..86bf16356d 100644
--- a/src/tmpfiles/tmpfiles.c
+++ b/src/tmpfiles/tmpfiles.c
@@ -172,6 +172,8 @@ typedef struct Item {
bool purge:1;
+ bool ignore_if_target_missing:1;
+
OperationMask done;
} Item;
@@ -440,6 +442,10 @@ static bool takes_ownership(ItemType t) {
RECURSIVE_REMOVE_PATH);
}
+static bool supports_ignore_if_target_missing(ItemType t) {
+ return t == CREATE_SYMLINK;
+}
+
static struct Item* find_glob(OrderedHashmap *h, const char *match) {
ItemArray *j;
@@ -2400,6 +2406,17 @@ static int create_symlink(Context *c, Item *i) {
assert(c);
assert(i);
+ if (i->ignore_if_target_missing) {
+ r = chase(i->argument, arg_root, CHASE_SAFE|CHASE_PREFIX_ROOT|CHASE_NOFOLLOW, /*ret_path=*/ NULL, /*ret_fd=*/ NULL);
+ if (r == -ENOENT) {
+ /* Silently skip over lines where the source file is missing. */
+ log_info("Symlink source path '%s%s' does not exist, skipping line.", strempty(arg_root), i->argument);
+ return 0;
+ }
+ if (r < 0)
+ return log_error_errno(r, "Failed to check if symlink source path '%s%s' exists: %m", strempty(arg_root), i->argument);
+ }
+
r = path_extract_filename(i->path, &bn);
if (r < 0)
return log_error_errno(r, "Failed to extract filename from path '%s': %m", i->path);
@@ -3593,7 +3610,8 @@ static int parse_line(
ItemArray *existing;
OrderedHashmap *h;
bool append_or_force = false, boot = false, allow_failure = false, try_replace = false,
- unbase64 = false, from_cred = false, missing_user_or_group = false, purge = false;
+ unbase64 = false, from_cred = false, missing_user_or_group = false, purge = false,
+ ignore_if_target_missing = false;
int r;
assert(fname);
@@ -3661,6 +3679,8 @@ static int parse_line(
from_cred = true;
else if (action[pos] == '$' && !purge)
purge = true;
+ else if (action[pos] == '?' && !ignore_if_target_missing)
+ ignore_if_target_missing = true;
else {
*invalid_config = true;
return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
@@ -3678,6 +3698,7 @@ static int parse_line(
i.allow_failure = allow_failure;
i.try_replace = try_replace;
i.purge = purge;
+ i.ignore_if_target_missing = ignore_if_target_missing;
r = specifier_printf(path, PATH_MAX-1, specifier_table, arg_root, NULL, &i.path);
if (ERRNO_IS_NEG_NOINFO(r))
@@ -3838,6 +3859,12 @@ static int parse_line(
"Purge flag '$' combined with line type '%c' which does not support purging.", (char) i.type);
}
+ if (i.ignore_if_target_missing && !supports_ignore_if_target_missing(i.type)) {
+ *invalid_config = true;
+ return log_syntax(NULL, LOG_ERR, fname, line, SYNTHETIC_ERRNO(EBADMSG),
+ "Modifier '?' combined with line type '%c' which does not support this modifier.", (char) i.type);
+ }
+
if (!should_include_path(i.path))
return 0;
@@ -3861,6 +3888,7 @@ static int parse_line(
if (!i.argument)
return log_oom();
}
+
break;
case COPY_FILES:
diff --git a/test/units/TEST-22-TMPFILES.21.sh b/test/units/TEST-22-TMPFILES.21.sh
new file mode 100755
index 0000000000..ffdaf3680b
--- /dev/null
+++ b/test/units/TEST-22-TMPFILES.21.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# shellcheck disable=SC2235
+set -eux
+
+# Test L?
+
+rm -rf /tmp/tmpfiles
+
+root="/tmp/tmpfiles"
+mkdir "$root"
+touch "$root/abc"
+
+SYSTEMD_LOG_LEVEL=debug systemd-tmpfiles --create - --root=$root <<EOF
+L? /i-dont-exist - - - - /def
+L? /i-do-exist - - - - /abc
+EOF
+
+(! test -L "$root/i-dont-exist")
+test -L "$root/i-do-exist"