diff options
author | Daan De Meyer <daan.j.demeyer@gmail.com> | 2024-11-04 12:21:21 +0100 |
---|---|---|
committer | Daan De Meyer <daan.j.demeyer@gmail.com> | 2024-11-04 19:04:21 +0100 |
commit | b5dc805583bbb019c6bf4c73bf0814a396dc0f12 (patch) | |
tree | 5c72a94907194ed3bb04377fed4c0569994774bd | |
parent | efi-loader: add missing stub for efi_stub_get_device_part_uuid() (diff) | |
download | systemd-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.xml | 16 | ||||
-rw-r--r-- | src/tmpfiles/tmpfiles.c | 30 | ||||
-rwxr-xr-x | test/units/TEST-22-TMPFILES.21.sh | 20 |
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" |