c2dfb7
From 3569b29eb8b082229dd97b8aae60bbe4d2f96ef5 Mon Sep 17 00:00:00 2001
c2dfb7
From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= <zbyszek@in.waw.pl>
c2dfb7
Date: Wed, 19 Dec 2018 23:05:48 +0100
c2dfb7
Subject: [PATCH] tmpfiles: fix crash with NULL in arg_root and other fixes and
c2dfb7
 tests
c2dfb7
c2dfb7
The function to replacement paths into the configuration file list was borked.
c2dfb7
Apart from the crash with empty root prefix, it would incorrectly handle the
c2dfb7
case where root *was* set, and the replacement file was supposed to override
c2dfb7
an existing file.
c2dfb7
c2dfb7
prefix_root is used instead of path_join because prefix_root removes duplicate
c2dfb7
slashes (when --root=dir/ is used).
c2dfb7
c2dfb7
A test is added.
c2dfb7
c2dfb7
Fixes #11124.
c2dfb7
c2dfb7
(cherry picked from commit 082bb1c59bd4300bcdc08488c94109680cfadf57)
c2dfb7
c2dfb7
Resolves: #1836024
c2dfb7
---
c2dfb7
 src/basic/conf-files.c     | 21 ++++++++-----
c2dfb7
 src/test/test-conf-files.c | 61 +++++++++++++++++++++++++++++++++++++-
c2dfb7
 2 files changed, 73 insertions(+), 9 deletions(-)
c2dfb7
c2dfb7
diff --git a/src/basic/conf-files.c b/src/basic/conf-files.c
c2dfb7
index d6ef0e941e..5ca83091c9 100644
c2dfb7
--- a/src/basic/conf-files.c
c2dfb7
+++ b/src/basic/conf-files.c
c2dfb7
@@ -204,14 +204,17 @@ int conf_files_insert(char ***strv, const char *root, char **dirs, const char *p
c2dfb7
                 if (c == 0) {
c2dfb7
                         char **dir;
c2dfb7
 
c2dfb7
-                        /* Oh, we found our spot and it already contains something. */
c2dfb7
+                        /* Oh, there already is an entry with a matching name (the last component). */
c2dfb7
+
c2dfb7
                         STRV_FOREACH(dir, dirs) {
c2dfb7
+                                _cleanup_free_ char *rdir = NULL;
c2dfb7
                                 char *p1, *p2;
c2dfb7
 
c2dfb7
-                                p1 = path_startswith((*strv)[i], root);
c2dfb7
-                                if (p1)
c2dfb7
-                                        /* Skip "/" in *dir, because p1 is without "/" too */
c2dfb7
-                                        p1 = path_startswith(p1, *dir + 1);
c2dfb7
+                                rdir = prefix_root(root, *dir);
c2dfb7
+                                if (!rdir)
c2dfb7
+                                        return -ENOMEM;
c2dfb7
+
c2dfb7
+                                p1 = path_startswith((*strv)[i], rdir);
c2dfb7
                                 if (p1)
c2dfb7
                                         /* Existing entry with higher priority
c2dfb7
                                          * or same priority, no need to do anything. */
c2dfb7
@@ -220,7 +223,8 @@ int conf_files_insert(char ***strv, const char *root, char **dirs, const char *p
c2dfb7
                                 p2 = path_startswith(path, *dir);
c2dfb7
                                 if (p2) {
c2dfb7
                                         /* Our new entry has higher priority */
c2dfb7
-                                        t = path_join(root, path, NULL);
c2dfb7
+
c2dfb7
+                                        t = prefix_root(root, path);
c2dfb7
                                         if (!t)
c2dfb7
                                                 return log_oom();
c2dfb7
 
c2dfb7
@@ -236,7 +240,8 @@ int conf_files_insert(char ***strv, const char *root, char **dirs, const char *p
c2dfb7
                 /* … we are not there yet, let's continue */
c2dfb7
         }
c2dfb7
 
c2dfb7
-        t = path_join(root, path, NULL);
c2dfb7
+        /* The new file has lower priority than all the existing entries */
c2dfb7
+        t = prefix_root(root, path);
c2dfb7
         if (!t)
c2dfb7
                 return log_oom();
c2dfb7
 
c2dfb7
@@ -322,7 +327,7 @@ int conf_files_list_with_replacement(
c2dfb7
                 if (r < 0)
c2dfb7
                         return log_error_errno(r, "Failed to extend config file list: %m");
c2dfb7
 
c2dfb7
-                p = path_join(root, replacement, NULL);
c2dfb7
+                p = prefix_root(root, replacement);
c2dfb7
                 if (!p)
c2dfb7
                         return log_oom();
c2dfb7
         }
c2dfb7
diff --git a/src/test/test-conf-files.c b/src/test/test-conf-files.c
c2dfb7
index 2ec2dfc261..5789767161 100644
c2dfb7
--- a/src/test/test-conf-files.c
c2dfb7
+++ b/src/test/test-conf-files.c
c2dfb7
@@ -13,6 +13,7 @@
c2dfb7
 #include "macro.h"
c2dfb7
 #include "mkdir.h"
c2dfb7
 #include "parse-util.h"
c2dfb7
+#include "path-util.h"
c2dfb7
 #include "rm-rf.h"
c2dfb7
 #include "string-util.h"
c2dfb7
 #include "strv.h"
c2dfb7
@@ -42,7 +43,7 @@ static void test_conf_files_list(bool use_root) {
c2dfb7
         _cleanup_strv_free_ char **found_files = NULL, **found_files2 = NULL;
c2dfb7
         const char *root_dir, *search_1, *search_2, *expect_a, *expect_b, *expect_c, *mask;
c2dfb7
 
c2dfb7
-        log_debug("/* %s */", __func__);
c2dfb7
+        log_debug("/* %s(%s) */", __func__, yes_no(use_root));
c2dfb7
 
c2dfb7
         setup_test_dir(tmp_dir,
c2dfb7
                        "/dir1/a.conf",
c2dfb7
@@ -92,6 +93,60 @@ static void test_conf_files_list(bool use_root) {
c2dfb7
         assert_se(rm_rf(tmp_dir, REMOVE_ROOT|REMOVE_PHYSICAL) == 0);
c2dfb7
 }
c2dfb7
 
c2dfb7
+static void test_conf_files_insert(const char *root) {
c2dfb7
+        _cleanup_strv_free_ char **s = NULL;
c2dfb7
+
c2dfb7
+        log_info("/* %s root=%s */", __func__, strempty(root));
c2dfb7
+
c2dfb7
+        char **dirs = STRV_MAKE("/dir1", "/dir2", "/dir3");
c2dfb7
+
c2dfb7
+        _cleanup_free_ const char
c2dfb7
+                *foo1 = prefix_root(root, "/dir1/foo.conf"),
c2dfb7
+                *foo2 = prefix_root(root, "/dir2/foo.conf"),
c2dfb7
+                *bar2 = prefix_root(root, "/dir2/bar.conf"),
c2dfb7
+                *zzz3 = prefix_root(root, "/dir3/zzz.conf"),
c2dfb7
+                *whatever = prefix_root(root, "/whatever.conf");
c2dfb7
+
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir2/foo.conf") == 0);
c2dfb7
+        assert_se(strv_equal(s, STRV_MAKE(foo2)));
c2dfb7
+
c2dfb7
+        /* The same file again, https://github.com/systemd/systemd/issues/11124 */
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir2/foo.conf") == 0);
c2dfb7
+        assert_se(strv_equal(s, STRV_MAKE(foo2)));
c2dfb7
+
c2dfb7
+        /* Lower priority → new entry is ignored */
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir3/foo.conf") == 0);
c2dfb7
+        assert_se(strv_equal(s, STRV_MAKE(foo2)));
c2dfb7
+
c2dfb7
+        /* Higher priority → new entry replaces */
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir1/foo.conf") == 0);
c2dfb7
+        assert_se(strv_equal(s, STRV_MAKE(foo1)));
c2dfb7
+
c2dfb7
+        /* Earlier basename */
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir2/bar.conf") == 0);
c2dfb7
+        assert_se(strv_equal(s, STRV_MAKE(bar2, foo1)));
c2dfb7
+
c2dfb7
+        /* Later basename */
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir3/zzz.conf") == 0);
c2dfb7
+        assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, zzz3)));
c2dfb7
+
c2dfb7
+        /* All lower priority → all ignored */
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir3/zzz.conf") == 0);
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir2/bar.conf") == 0);
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir3/bar.conf") == 0);
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir2/foo.conf") == 0);
c2dfb7
+        assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, zzz3)));
c2dfb7
+
c2dfb7
+        /* Two entries that don't match any of the directories, but match basename */
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/dir4/zzz.conf") == 0);
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/zzz.conf") == 0);
c2dfb7
+        assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, zzz3)));
c2dfb7
+
c2dfb7
+        /* An entry that doesn't match any of the directories, no match at all */
c2dfb7
+        assert_se(conf_files_insert(&s, root, dirs, "/whatever.conf") == 0);
c2dfb7
+        assert_se(strv_equal(s, STRV_MAKE(bar2, foo1, whatever, zzz3)));
c2dfb7
+}
c2dfb7
+
c2dfb7
 int main(int argc, char **argv) {
c2dfb7
         log_set_max_level(LOG_DEBUG);
c2dfb7
         log_parse_environment();
c2dfb7
@@ -99,5 +154,9 @@ int main(int argc, char **argv) {
c2dfb7
 
c2dfb7
         test_conf_files_list(false);
c2dfb7
         test_conf_files_list(true);
c2dfb7
+        test_conf_files_insert(NULL);
c2dfb7
+        test_conf_files_insert("/root");
c2dfb7
+        test_conf_files_insert("/root/");
c2dfb7
+
c2dfb7
         return 0;
c2dfb7
 }