From d49d646d00078b201cdde2978b7941d20acb1d4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Wed, 2 Mar 2022 16:53:54 +0100 Subject: [PATCH] basic/unit-file: split out the subroutine for symlink verification The old logs used __func__, but this doesn't make sense now, because the low-level function will be used in other places. So those are adjusted to be more generic. (cherry picked from commit 9825181143530af7003fc50567b814dbbee39046) Related: #2082131 --- src/basic/unit-file.c | 159 +++++++++++++++++++++++++----------------- 1 file changed, 96 insertions(+), 63 deletions(-) diff --git a/src/basic/unit-file.c b/src/basic/unit-file.c index 96826e2940..25abce932a 100644 --- a/src/basic/unit-file.c +++ b/src/basic/unit-file.c @@ -260,6 +260,83 @@ static int directory_name_is_valid(const char *name) { return false; } +static int unit_file_resolve_symlink( + const char *root_dir, + char **search_path, + const char *dir, + int dirfd, + const char *filename, + char **ret_destination) { + + _cleanup_free_ char *target = NULL, *simplified = NULL, *dst = NULL; + int r; + + assert(dir); + assert(dirfd >= 0); + assert(filename); + assert(ret_destination); + + r = readlinkat_malloc(dirfd, filename, &target); + if (r < 0) + return log_warning_errno(r, "Failed to read symlink %s%s%s: %m", + dir, dir ? "/" : "", filename); + + bool is_abs = path_is_absolute(target); + if (root_dir || !is_abs) { + char *target_abs = path_join(is_abs ? root_dir : dir, target); + if (!target_abs) + return log_oom(); + + free_and_replace(target, target_abs); + } + + /* Get rid of "." and ".." components in target path */ + r = chase_symlinks(target, root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL); + if (r < 0) + return log_warning_errno(r, "Failed to resolve symlink %s/%s pointing to %s: %m", + dir, filename, target); + + /* Check if the symlink goes outside of our search path. + * If yes, it's a linked unit file or mask, and we don't care about the target name. + * Let's just store the link source directly. + * If not, let's verify that it's a good symlink. */ + const char *tail = path_startswith_strv(simplified, search_path); + if (!tail) { + log_debug("Linked unit file: %s/%s → %s", dir, filename, simplified); + + dst = path_join(dir, filename); + if (!dst) + return log_oom(); + + } else { + r = path_extract_filename(simplified, &dst); + if (r < 0) + return r; + + bool self_alias = streq(dst, filename); + + if (is_path(tail)) + log_full(self_alias ? LOG_DEBUG : LOG_WARNING, + "Suspicious symlink %s/%s→%s, treating as alias.", + dir, filename, simplified); + + r = unit_validate_alias_symlink_and_warn(filename, simplified); + if (r < 0) + return r; + + if (self_alias) + /* A self-alias that has no effect */ + return log_debug_errno(SYNTHETIC_ERRNO(ELOOP), + "Unit file self-alias: %s/%s → %s, ignoring.", + dir, filename, dst); + + log_debug("Unit file alias: %s/%s → %s", dir, filename, dst); + } + + *ret_destination = TAKE_PTR(dst); + return 0; +} + int unit_file_build_name_map( const LookupPaths *lp, uint64_t *cache_timestamp_hash, @@ -310,10 +387,9 @@ int unit_file_build_name_map( FOREACH_DIRENT_ALL(de, d, log_warning_errno(errno, "Failed to read \"%s\", ignoring: %m", *dir)) { _unused_ _cleanup_free_ char *_filename_free = NULL; - _cleanup_free_ char *simplified = NULL; - bool symlink_to_dir = false; - const char *dst = NULL; char *filename; + _cleanup_free_ char *dst = NULL; + bool symlink_to_dir = false; /* We only care about valid units and dirs with certain suffixes, let's ignore the * rest. */ @@ -397,77 +473,34 @@ int unit_file_build_name_map( /* We don't explicitly check for alias loops here. unit_ids_map_get() which * limits the number of hops should be used to access the map. */ - _cleanup_free_ char *target = NULL; - - r = readlinkat_malloc(dirfd(d), de->d_name, &target); - if (r < 0) { - log_warning_errno(r, "Failed to read symlink %s/%s, ignoring: %m", - *dir, de->d_name); + r = unit_file_resolve_symlink(lp->root_dir, lp->search_path, + *dir, dirfd(d), de->d_name, + &dst); + if (r == -ENOMEM) + return r; + if (r < 0) /* we ignore other errors here */ continue; - } - const bool is_abs = path_is_absolute(target); - if (lp->root_dir || !is_abs) { - char *target_abs = path_join(is_abs ? lp->root_dir : *dir, target); - if (!target_abs) + } else { + dst = TAKE_PTR(_filename_free); /* Grab the copy we made previously, if available. */ + if (!dst) { + dst = strdup(filename); + if (!dst) return log_oom(); - - free_and_replace(target, target_abs); } - /* Get rid of "." and ".." components in target path */ - r = chase_symlinks(target, lp->root_dir, CHASE_NOFOLLOW | CHASE_NONEXISTENT, &simplified, NULL); - if (r < 0) { - log_warning_errno(r, "Failed to resolve symlink %s pointing to %s, ignoring: %m", - filename, target); - continue; - } - - /* Check if the symlink goes outside of our search path. - * If yes, it's a linked unit file or mask, and we don't care about the target name. - * Let's just store the link source directly. - * If not, let's verify that it's a good symlink. */ - char *tail = path_startswith_strv(simplified, lp->search_path); - if (!tail) { - log_debug("%s: linked unit file: %s → %s", - __func__, filename, simplified); - - dst = filename; - } else { - - bool self_alias; - - dst = basename(simplified); - self_alias = streq(dst, de->d_name); - - if (is_path(tail)) - log_full(self_alias ? LOG_DEBUG : LOG_WARNING, - "Suspicious symlink %s→%s, treating as alias.", - filename, simplified); - - r = unit_validate_alias_symlink_and_warn(filename, simplified); - if (r < 0) - continue; - - if (self_alias) { - /* A self-alias that has no effect */ - log_debug("%s: self-alias: %s/%s → %s, ignoring.", - __func__, *dir, de->d_name, dst); - continue; - } - - log_debug("%s: alias: %s/%s → %s", __func__, *dir, de->d_name, dst); - } - - } else { - dst = filename; log_debug("%s: normal unit file: %s", __func__, dst); } - r = hashmap_put_strdup(&ids, de->d_name, dst); + _cleanup_free_ char *key = strdup(de->d_name); + if (!key) + return log_oom(); + + r = hashmap_ensure_put(&ids, &string_hash_ops_free_free, key, dst); if (r < 0) return log_warning_errno(r, "Failed to add entry to hashmap (%s→%s): %m", de->d_name, dst); + key = dst = NULL; } }