mirror of
https://github.com/systemd/systemd.git
synced 2024-12-22 17:35:35 +03:00
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.
This commit is contained in:
parent
c52f6c1f33
commit
b5dc805583
@ -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>
|
||||
|
@ -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:
|
||||
|
20
test/units/TEST-22-TMPFILES.21.sh
Executable file
20
test/units/TEST-22-TMPFILES.21.sh
Executable file
@ -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"
|
Loading…
Reference in New Issue
Block a user