8d419f
From dc017e5c51e61ddd96d2a94f35223ac7788c8454 Mon Sep 17 00:00:00 2001
8d419f
From: Andreas Rammhold <andreas@rammhold.de>
8d419f
Date: Wed, 18 Aug 2021 19:10:08 +0200
8d419f
Subject: [PATCH] core: handle lookup paths being symlinks
8d419f
MIME-Version: 1.0
8d419f
Content-Type: text/plain; charset=UTF-8
8d419f
Content-Transfer-Encoding: 8bit
8d419f
8d419f
With a recent change paths leaving the statically known lookup paths would be
8d419f
treated differently then those that remained within those. That was done
8d419f
(AFAIK) to consistently handle alias names. Unfortunately that means that on
8d419f
some distributions, especially those where /etc/ consists mostly of symlinks,
8d419f
would trigger that new detection for every single unit in /etc/systemd/system.
8d419f
The reason for that is that the units directory itself is already a symlink.
8d419f
8d419f
Rebased-by: Zbigniew Jędrzejewski-Szmek <zbyszek@in.waw.pl>
8d419f
(cherry picked from commit 66c38cd0536c50769eba6abccf383bbaceb268ca)
8d419f
8d419f
Resolves: #2082131
8d419f
---
8d419f
 src/basic/unit-file.c | 41 ++++++++++++++++++++++++++++++++++++++++-
8d419f
 1 file changed, 40 insertions(+), 1 deletion(-)
8d419f
8d419f
diff --git a/src/basic/unit-file.c b/src/basic/unit-file.c
8d419f
index 7c1ae515e1..83c29bb25f 100644
8d419f
--- a/src/basic/unit-file.c
8d419f
+++ b/src/basic/unit-file.c
8d419f
@@ -388,6 +388,7 @@ int unit_file_build_name_map(
8d419f
 
8d419f
         _cleanup_hashmap_free_ Hashmap *ids = NULL, *names = NULL;
8d419f
         _cleanup_set_free_free_ Set *paths = NULL;
8d419f
+        _cleanup_strv_free_ char **expanded_search_path = NULL;
8d419f
         uint64_t timestamp_hash;
8d419f
         int r;
8d419f
 
8d419f
@@ -406,6 +407,44 @@ int unit_file_build_name_map(
8d419f
                         return log_oom();
8d419f
         }
8d419f
 
8d419f
+        /* Go over all our search paths, chase their symlinks and store the result in the
8d419f
+         * expanded_search_path list.
8d419f
+         *
8d419f
+         * This is important for cases where any of the unit directories itself are symlinks into other
8d419f
+         * directories and would therefore cause all of the unit files to be recognized as linked units.
8d419f
+         *
8d419f
+         * This is important for distributions such as NixOS where most paths in /etc/ are symlinks to some
8d419f
+         * other location on the filesystem (e.g.  into /nix/store/).
8d419f
+         *
8d419f
+         * Search paths are ordered by priority (highest first), and we need to maintain this order.
8d419f
+         * If a resolved path is already in the list, we don't need to include.
8d419f
+         *
8d419f
+         * Note that we build a list that contains both the original paths and the resolved symlinks:
8d419f
+         * we need the latter for the case where the directory is symlinked, as described above, and
8d419f
+         * the former for the case where some unit file alias is a dangling symlink that points to one
8d419f
+         * of the "original" directories (and can't be followed).
8d419f
+         */
8d419f
+        STRV_FOREACH(dir, lp->search_path) {
8d419f
+                _cleanup_free_ char *resolved_dir = NULL;
8d419f
+
8d419f
+                r = strv_extend(&expanded_search_path, *dir);
8d419f
+                if (r < 0)
8d419f
+                        return log_oom();
8d419f
+
8d419f
+                r = chase_symlinks(*dir, NULL, 0, &resolved_dir, NULL);
8d419f
+                if (r < 0) {
8d419f
+                        if (r != -ENOENT)
8d419f
+                                log_warning_errno(r, "Failed to resolve symlink %s, ignoring: %m", *dir);
8d419f
+                        continue;
8d419f
+                }
8d419f
+
8d419f
+                if (strv_contains(expanded_search_path, resolved_dir))
8d419f
+                        continue;
8d419f
+
8d419f
+                if (strv_consume(&expanded_search_path, TAKE_PTR(resolved_dir)) < 0)
8d419f
+                        return log_oom();
8d419f
+        }
8d419f
+
8d419f
         STRV_FOREACH(dir, lp->search_path) {
8d419f
                 _cleanup_closedir_ DIR *d = NULL;
8d419f
 
8d419f
@@ -504,7 +543,7 @@ int unit_file_build_name_map(
8d419f
                                 /* We don't explicitly check for alias loops here. unit_ids_map_get() which
8d419f
                                  * limits the number of hops should be used to access the map. */
8d419f
 
8d419f
-                                r = unit_file_resolve_symlink(lp->root_dir, lp->search_path,
8d419f
+                                r = unit_file_resolve_symlink(lp->root_dir, expanded_search_path,
8d419f
                                                               *dir, dirfd(d), de->d_name,
8d419f
                                                               /* resolve_destination_target= */ false,
8d419f
                                                               &dst);