ryantimwilson / rpms / systemd

Forked from rpms/systemd 4 months ago
Clone
8d419f
From 56bc8e8eef5fcbfcf72dd1b3caa56b9186e1011d Mon Sep 17 00:00:00 2001
8d419f
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
8d419f
Date: Fri, 25 Mar 2022 15:43:27 +0100
8d419f
Subject: [PATCH] shared/install: when creating symlinks, accept different but
8d419f
 equivalent symlinks
8d419f
MIME-Version: 1.0
8d419f
Content-Type: text/plain; charset=UTF-8
8d419f
Content-Transfer-Encoding: 8bit
8d419f
8d419f
We would only accept "identical" links, but having e.g. a symlink
8d419f
/usr/lib/systemd/system/foo-alias.service → /usr/lib/systemd/system/foo.service
8d419f
when we're trying to create /usr/lib/systemd/system/foo-alias.service →
8d419f
./foo.service is OK. This fixes an issue found in ubuntuautopkg package
8d419f
installation, where we'd fail when enabling systemd-resolved.service, because
8d419f
the existing alias was absolute, and (with the recent patches) we were trying
8d419f
to create a relative one.
8d419f
8d419f
A test is added.
8d419f
(For .wants/.requires symlinks we were already doing OK. A test is also
8d419f
added, to verify.)
8d419f
8d419f
(cherry picked from commit 3fc53351dc8f37355f5a4ee8f922d3e13a5182c2)
8d419f
8d419f
Related: #2082131
8d419f
---
8d419f
 src/shared/install.c          | 59 ++++++++++++++++++++++++++---------
8d419f
 test/test-systemctl-enable.sh | 39 +++++++++++++++++++++--
8d419f
 2 files changed, 81 insertions(+), 17 deletions(-)
8d419f
8d419f
diff --git a/src/shared/install.c b/src/shared/install.c
8d419f
index d6951b805d..22b16ad453 100644
8d419f
--- a/src/shared/install.c
8d419f
+++ b/src/shared/install.c
8d419f
@@ -423,21 +423,54 @@ void unit_file_dump_changes(int r, const char *verb, const UnitFileChange *chang
8d419f
 }
8d419f
 
8d419f
 /**
8d419f
- * Checks if two paths or symlinks from wd are the same, when root is the root of the filesystem.
8d419f
- * wc should be the full path in the host file system.
8d419f
+ * Checks if two symlink targets (starting from src) are equivalent as far as the unit enablement logic is
8d419f
+ * concerned. If the target is in the unit search path, then anything with the same name is equivalent.
8d419f
+ * If outside the unit search path, paths must be identical.
8d419f
  */
8d419f
-static bool chroot_symlinks_same(const char *root, const char *wd, const char *a, const char *b) {
8d419f
-        assert(path_is_absolute(wd));
8d419f
+static int chroot_unit_symlinks_equivalent(
8d419f
+                const LookupPaths *lp,
8d419f
+                const char *src,
8d419f
+                const char *target_a,
8d419f
+                const char *target_b) {
8d419f
+
8d419f
+        assert(lp);
8d419f
+        assert(src);
8d419f
+        assert(target_a);
8d419f
+        assert(target_b);
8d419f
 
8d419f
         /* This will give incorrect results if the paths are relative and go outside
8d419f
          * of the chroot. False negatives are possible. */
8d419f
 
8d419f
-        if (!root)
8d419f
-                root = "/";
8d419f
+        const char *root = lp->root_dir ?: "/";
8d419f
+        _cleanup_free_ char *dirname = NULL;
8d419f
+        int r;
8d419f
+
8d419f
+        if (!path_is_absolute(target_a) || !path_is_absolute(target_b)) {
8d419f
+                r = path_extract_directory(src, &dirname);
8d419f
+                if (r < 0)
8d419f
+                        return r;
8d419f
+        }
8d419f
 
8d419f
-        a = strjoina(path_is_absolute(a) ? root : wd, "/", a);
8d419f
-        b = strjoina(path_is_absolute(b) ? root : wd, "/", b);
8d419f
-        return path_equal_or_files_same(a, b, 0);
8d419f
+        _cleanup_free_ char *a = path_join(path_is_absolute(target_a) ? root : dirname, target_a);
8d419f
+        _cleanup_free_ char *b = path_join(path_is_absolute(target_b) ? root : dirname, target_b);
8d419f
+        if (!a || !b)
8d419f
+                return log_oom();
8d419f
+
8d419f
+        r = path_equal_or_files_same(a, b, 0);
8d419f
+        if (r != 0)
8d419f
+                return r;
8d419f
+
8d419f
+        _cleanup_free_ char *a_name = NULL, *b_name = NULL;
8d419f
+        r = path_extract_filename(a, &a_name);
8d419f
+        if (r < 0)
8d419f
+                return r;
8d419f
+        r = path_extract_filename(b, &b_name);
8d419f
+        if (r < 0)
8d419f
+                return r;
8d419f
+
8d419f
+        return streq(a_name, b_name) &&
8d419f
+               path_startswith_strv(a, lp->search_path) &&
8d419f
+               path_startswith_strv(b, lp->search_path);
8d419f
 }
8d419f
 
8d419f
 static int create_symlink(
8d419f
@@ -448,7 +481,7 @@ static int create_symlink(
8d419f
                 UnitFileChange **changes,
8d419f
                 size_t *n_changes) {
8d419f
 
8d419f
-        _cleanup_free_ char *dest = NULL, *dirname = NULL;
8d419f
+        _cleanup_free_ char *dest = NULL;
8d419f
         const char *rp;
8d419f
         int r;
8d419f
 
8d419f
@@ -489,11 +522,7 @@ static int create_symlink(
8d419f
                 return r;
8d419f
         }
8d419f
 
8d419f
-        dirname = dirname_malloc(new_path);
8d419f
-        if (!dirname)
8d419f
-                return -ENOMEM;
8d419f
-
8d419f
-        if (chroot_symlinks_same(lp->root_dir, dirname, dest, old_path)) {
8d419f
+        if (chroot_unit_symlinks_equivalent(lp, new_path, dest, old_path)) {
8d419f
                 log_debug("Symlink %s → %s already exists", new_path, dest);
8d419f
                 return 1;
8d419f
         }
8d419f
diff --git a/test/test-systemctl-enable.sh b/test/test-systemctl-enable.sh
8d419f
index 3b30f090a5..0f66af309a 100644
8d419f
--- a/test/test-systemctl-enable.sh
8d419f
+++ b/test/test-systemctl-enable.sh
8d419f
@@ -39,8 +39,29 @@ test -h "$root/etc/systemd/system/default.target.wants/test1.service"
8d419f
 test -h "$root/etc/systemd/system/special.target.requires/test1.service"
8d419f
 
8d419f
 "$systemctl" --root="$root" disable test1.service
8d419f
-test ! -e "$root/etc/systemd/system/default.target.wants/test1.service"
8d419f
-test ! -e "$root/etc/systemd/system/special.target.requires/test1.service"
8d419f
+test ! -h "$root/etc/systemd/system/default.target.wants/test1.service"
8d419f
+test ! -h "$root/etc/systemd/system/special.target.requires/test1.service"
8d419f
+
8d419f
+: '------enable when link already exists-----------------------'
8d419f
+# We don't read the symlink target, so it's OK for the symlink to point
8d419f
+# to something else. We should just silently accept this.
8d419f
+
8d419f
+mkdir -p "$root/etc/systemd/system/default.target.wants"
8d419f
+mkdir -p "$root/etc/systemd/system/special.target.requires"
8d419f
+ln -s /usr/lib/systemd/system/test1.service "$root/etc/systemd/system/default.target.wants/test1.service"
8d419f
+ln -s /usr/lib/systemd/system/test1.service "$root/etc/systemd/system/special.target.requires/test1.service"
8d419f
+
8d419f
+"$systemctl" --root="$root" enable test1.service
8d419f
+test -h "$root/etc/systemd/system/default.target.wants/test1.service"
8d419f
+test -h "$root/etc/systemd/system/special.target.requires/test1.service"
8d419f
+
8d419f
+"$systemctl" --root="$root" reenable test1.service
8d419f
+test -h "$root/etc/systemd/system/default.target.wants/test1.service"
8d419f
+test -h "$root/etc/systemd/system/special.target.requires/test1.service"
8d419f
+
8d419f
+"$systemctl" --root="$root" disable test1.service
8d419f
+test ! -h "$root/etc/systemd/system/default.target.wants/test1.service"
8d419f
+test ! -h "$root/etc/systemd/system/special.target.requires/test1.service"
8d419f
 
8d419f
 : '------suffix guessing---------------------------------------'
8d419f
 "$systemctl" --root="$root" enable test1
8d419f
@@ -90,6 +111,20 @@ test ! -h "$root/etc/systemd/system/default.target.wants/test1.service"
8d419f
 test ! -h "$root/etc/systemd/system/special.target.requires/test1.service"
8d419f
 test ! -h "$root/etc/systemd/system/test1-goodalias.service"
8d419f
 
8d419f
+: '-------aliases when link already exists---------------------'
8d419f
+cat >"$root/etc/systemd/system/test1a.service" <
8d419f
+[Install]
8d419f
+Alias=test1a-alias.service
8d419f
+EOF
8d419f
+
8d419f
+ln -s /usr/lib/systemd/system/test1a.service "$root/etc/systemd/system/test1a-alias.service"
8d419f
+
8d419f
+"$systemctl" --root="$root" enable test1a.service
8d419f
+test -h "$root/etc/systemd/system/test1a-alias.service"
8d419f
+
8d419f
+"$systemctl" --root="$root" disable test1a.service
8d419f
+test ! -h "$root/etc/systemd/system/test1a-alias.service"
8d419f
+
8d419f
 : '-------also units-------------------------------------------'
8d419f
 cat >"$root/etc/systemd/system/test2.socket" <
8d419f
 [Install]