From: Franck Bui Date: Thu, 16 Nov 2017 10:27:29 +0000 (+0100) Subject: tmpfiles: when /etc is not fully initialized, some specifiers are expected to be... X-Git-Tag: v236~202 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=4cef1923571de409496567b2c4777aea4676ff00;p=thirdparty%2Fsystemd.git tmpfiles: when /etc is not fully initialized, some specifiers are expected to be unresolvable (#6860) In chroot environments, /etc might not be fully initialized: /etc/machine-id can be missing for example. This makes the expansions of affected specifiers impossible at that time. These cases should not be considered as errors and such failures shouldn't be logged at an error level therefore this patch downgrades the level used to LOG_NOTICE in such cases. Also this is logged at LOG_NOTICE only the first time and then downgrade to LOG_DEBUG for the rest. That way, if debugging is enabled we get the full output, but otherwise we only see only one message. The expansion of specifiers is now self contained in a dedicated function instead of being spread all over the place. --- diff --git a/src/tmpfiles/tmpfiles.c b/src/tmpfiles/tmpfiles.c index 7f457ca36e2..a21127b600f 100644 --- a/src/tmpfiles/tmpfiles.c +++ b/src/tmpfiles/tmpfiles.c @@ -165,14 +165,49 @@ static const char conf_file_dirs[] = CONF_PATHS_NULSTR("tmpfiles.d"); static OrderedHashmap *items = NULL, *globs = NULL; static Set *unix_sockets = NULL; +static int specifier_machine_id_safe(char specifier, void *data, void *userdata, char **ret); + static const Specifier specifier_table[] = { - { 'm', specifier_machine_id, NULL }, + { 'm', specifier_machine_id_safe, NULL }, { 'b', specifier_boot_id, NULL }, { 'H', specifier_host_name, NULL }, { 'v', specifier_kernel_release, NULL }, {} }; +static int specifier_machine_id_safe(char specifier, void *data, void *userdata, char **ret) { + int r; + + /* If /etc/machine_id is missing (e.g. in a chroot environment), returns + * a recognizable error so that the caller can skip the rule + * gracefully. */ + + r = specifier_machine_id(specifier, data, userdata, ret); + if (r == -ENOENT) + return -ENOKEY; + + return r; +} + +static int log_unresolvable_specifier(const char *filename, unsigned line) { + static bool notified = false; + + /* This is called when /etc is not fully initialized (e.g. in a chroot + * environment) where some specifiers are unresolvable. These cases are + * not considered as an error so log at LOG_NOTICE only for the first + * time and then downgrade this to LOG_DEBUG for the rest. */ + + log_full(notified ? LOG_DEBUG : LOG_NOTICE, + "[%s:%u] Failed to resolve specifier: uninitialized /etc detected, skipping", + filename, line); + + if (!notified) + log_notice("All rules containing unresolvable specifiers will be skipped."); + + notified = true; + return 0; +} + static bool needs_glob(ItemType t) { return IN_SET(t, WRITE_FILE, @@ -693,7 +728,7 @@ static int parse_xattrs_from_arg(Item *i) { p = i->argument; for (;;) { - _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL, *xattr_replaced = NULL; + _cleanup_free_ char *name = NULL, *value = NULL, *xattr = NULL; r = extract_first_word(&p, &xattr, NULL, EXTRACT_QUOTES|EXTRACT_CUNESCAPE); if (r < 0) @@ -701,11 +736,7 @@ static int parse_xattrs_from_arg(Item *i) { if (r <= 0) break; - r = specifier_printf(xattr, specifier_table, NULL, &xattr_replaced); - if (r < 0) - return log_error_errno(r, "Failed to replace specifiers in extended attribute '%s': %m", xattr); - - r = split_pair(xattr_replaced, "=", &name, &value); + r = split_pair(xattr, "=", &name, &value); if (r < 0) { log_warning_errno(r, "Failed to parse extended attribute, ignoring: %s", xattr); continue; @@ -1024,19 +1055,9 @@ static int write_one_file(Item *i, const char *path) { } if (i->argument) { - _cleanup_free_ char *unescaped = NULL, *replaced = NULL; - log_debug("%s to \"%s\".", i->type == CREATE_FILE ? "Appending" : "Writing", path); - r = cunescape(i->argument, 0, &unescaped); - if (r < 0) - return log_error_errno(r, "Failed to unescape parameter to write: %s", i->argument); - - r = specifier_printf(unescaped, specifier_table, NULL, &replaced); - if (r < 0) - return log_error_errno(r, "Failed to replace specifiers in parameter to write '%s': %m", unescaped); - - r = loop_write(fd, replaced, strlen(replaced), false); + r = loop_write(fd, i->argument, strlen(i->argument), false); if (r < 0) return log_error_errno(r, "Failed to write file \"%s\": %m", path); } else @@ -1145,7 +1166,6 @@ static const char *creation_mode_verb_table[_CREATION_MODE_MAX] = { DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(creation_mode_verb, CreationMode); static int create_item(Item *i) { - _cleanup_free_ char *resolved = NULL; struct stat st; int r = 0; int q = 0; @@ -1171,12 +1191,8 @@ static int create_item(Item *i) { break; case COPY_FILES: { - r = specifier_printf(i->argument, specifier_table, NULL, &resolved); - if (r < 0) - return log_error_errno(r, "Failed to substitute specifiers in copy source %s: %m", i->argument); - - log_debug("Copying tree \"%s\" to \"%s\".", resolved, i->path); - r = copy_tree(resolved, i->path, i->uid_set ? i->uid : UID_INVALID, i->gid_set ? i->gid : GID_INVALID, COPY_REFLINK); + log_debug("Copying tree \"%s\" to \"%s\".", i->argument, i->path); + r = copy_tree(i->argument, i->path, i->uid_set ? i->uid : UID_INVALID, i->gid_set ? i->gid : GID_INVALID, COPY_REFLINK); if (r == -EROFS && stat(i->path, &st) == 0) r = -EEXIST; @@ -1187,8 +1203,8 @@ static int create_item(Item *i) { if (r != -EEXIST) return log_error_errno(r, "Failed to copy files to %s: %m", i->path); - if (stat(resolved, &a) < 0) - return log_error_errno(errno, "stat(%s) failed: %m", resolved); + if (stat(i->argument, &a) < 0) + return log_error_errno(errno, "stat(%s) failed: %m", i->argument); if (stat(i->path, &b) < 0) return log_error_errno(errno, "stat(%s) failed: %m", i->path); @@ -1343,26 +1359,22 @@ static int create_item(Item *i) { } case CREATE_SYMLINK: { - r = specifier_printf(i->argument, specifier_table, NULL, &resolved); - if (r < 0) - return log_error_errno(r, "Failed to substitute specifiers in symlink target %s: %m", i->argument); - mac_selinux_create_file_prepare(i->path, S_IFLNK); - r = symlink(resolved, i->path); + r = symlink(i->argument, i->path); mac_selinux_create_file_clear(); if (r < 0) { _cleanup_free_ char *x = NULL; if (errno != EEXIST) - return log_error_errno(errno, "symlink(%s, %s) failed: %m", resolved, i->path); + return log_error_errno(errno, "symlink(%s, %s) failed: %m", i->argument, i->path); r = readlink_malloc(i->path, &x); - if (r < 0 || !streq(resolved, x)) { + if (r < 0 || !streq(i->argument, x)) { if (i->force) { mac_selinux_create_file_prepare(i->path, S_IFLNK); - r = symlink_atomic(resolved, i->path); + r = symlink_atomic(i->argument, i->path); mac_selinux_create_file_clear(); if (IN_SET(r, -EEXIST, -ENOTEMPTY)) { @@ -1371,11 +1383,11 @@ static int create_item(Item *i) { return log_error_errno(r, "rm -fr %s failed: %m", i->path); mac_selinux_create_file_prepare(i->path, S_IFLNK); - r = symlink(resolved, i->path) < 0 ? -errno : 0; + r = symlink(i->argument, i->path) < 0 ? -errno : 0; mac_selinux_create_file_clear(); } if (r < 0) - return log_error_errno(r, "symlink(%s, %s) failed: %m", resolved, i->path); + return log_error_errno(r, "symlink(%s, %s) failed: %m", i->argument, i->path); creation = CREATION_FORCE; } else { @@ -1785,6 +1797,52 @@ static bool should_include_path(const char *path) { return false; } +static int specifier_expansion_from_arg(Item *i) { + _cleanup_free_ char *unescaped = NULL, *resolved = NULL; + char **xattr; + int r; + + assert(i); + + if (i->argument == NULL) + return 0; + + switch (i->type) { + case COPY_FILES: + case CREATE_SYMLINK: + case CREATE_FILE: + case TRUNCATE_FILE: + case WRITE_FILE: + r = cunescape(i->argument, 0, &unescaped); + if (r < 0) + return log_error_errno(r, "Failed to unescape parameter to write: %s", i->argument); + + r = specifier_printf(unescaped, specifier_table, NULL, &resolved); + if (r < 0) + return r; + + free_and_replace(i->argument, resolved); + break; + + case SET_XATTR: + case RECURSIVE_SET_XATTR: + assert(i->xattrs); + + STRV_FOREACH (xattr, i->xattrs) { + r = specifier_printf(*xattr, specifier_table, NULL, &resolved); + if (r < 0) + return r; + + free_and_replace(*xattr, resolved); + } + break; + + default: + break; + } + return 0; +} + static int parse_line(const char *fname, unsigned line, const char *buffer) { _cleanup_free_ char *action = NULL, *mode = NULL, *user = NULL, *group = NULL, *age = NULL, *path = NULL; @@ -1849,10 +1907,10 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { i.force = force; r = specifier_printf(path, specifier_table, NULL, &i.path); - if (r < 0) { - log_error("[%s:%u] Failed to replace specifiers: %s", fname, line, path); - return r; - } + if (r == -ENOKEY) + return log_unresolvable_specifier(fname, line); + if (r < 0) + return log_error_errno(r, "[%s:%u] Failed to replace specifiers: %s", fname, line, path); switch (i.type) { @@ -1973,6 +2031,13 @@ static int parse_line(const char *fname, unsigned line, const char *buffer) { if (!should_include_path(i.path)) return 0; + r = specifier_expansion_from_arg(&i); + if (r == -ENOKEY) + return log_unresolvable_specifier(fname, line); + if (r < 0) + return log_error_errno(r, "[%s:%u] Failed to substitute specifiers in argument: %m", + fname, line); + if (arg_root) { char *p;