From b5dc805583bbb019c6bf4c73bf0814a396dc0f12 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Mon, 4 Nov 2024 12:21:21 +0100 Subject: [PATCH] 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. --- man/tmpfiles.d.xml | 16 +++++++--------- src/tmpfiles/tmpfiles.c | 30 +++++++++++++++++++++++++++++- test/units/TEST-22-TMPFILES.21.sh | 20 ++++++++++++++++++++ 3 files changed, 56 insertions(+), 10 deletions(-) create mode 100755 test/units/TEST-22-TMPFILES.21.sh diff --git a/man/tmpfiles.d.xml b/man/tmpfiles.d.xml index a721c1e66d4..4e6652dc019 100644 --- a/man/tmpfiles.d.xml +++ b/man/tmpfiles.d.xml @@ -299,15 +299,13 @@ L /tmp/foobar - - - - /dev/null L L+ - Create a symlink if it does not exist - yet. If suffixed with + 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 - /usr/share/factory/ are created. Note - that permissions on symlinks are ignored. - + L? + Create a symlink if it does not exist yet. If suffixed with + + 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 ? 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 /usr/share/factory/ are created. Note that + permissions on symlinks are ignored. diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index c4e032d8d76..86bf16356dc 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 00000000000..ffdaf3680b8 --- /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 <