]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
tmpfiles: when /etc is not fully initialized, some specifiers are expected to be...
authorFranck Bui <fbui@suse.com>
Thu, 16 Nov 2017 10:27:29 +0000 (11:27 +0100)
committerLennart Poettering <lennart@poettering.net>
Thu, 16 Nov 2017 10:27:29 +0000 (11:27 +0100)
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.

src/tmpfiles/tmpfiles.c

index 7f457ca36e242ef3aa65787a5f98bb35c8fe6f05..a21127b600fd447743b07a01b50f343b83b8b7ff 100644 (file)
@@ -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;