summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLuca Boccassi <luca.boccassi@microsoft.com>2021-12-28 20:38:24 +0000
committerGitHub <noreply@github.com>2021-12-28 20:38:24 +0000
commit69b5c7cf2af8fa5f32a4623f325791d968789ca2 (patch)
treeb2fd20c58d678195e13b185b9e523fe8ee2b5bf1
parent4b3ad81bfafcd97acb06db463495e348d159d8e6 (diff)
parentcf6562e4565c3055e1f387adadf2ff7fb0ce1688 (diff)
downloadsystemd-69b5c7cf2af8fa5f32a4623f325791d968789ca2.tar.gz
Merge pull request #21925 from yuwata/unit-file-symlinked-drop-in-directory
unit-file: fix symlinked drop-in directory handling
-rw-r--r--src/basic/unit-file.c84
-rwxr-xr-xtest/units/testsuite-15.sh20
2 files changed, 81 insertions, 23 deletions
diff --git a/src/basic/unit-file.c b/src/basic/unit-file.c
index 30c632dfce..faea92f66d 100644
--- a/src/basic/unit-file.c
+++ b/src/basic/unit-file.c
@@ -236,6 +236,31 @@ bool lookup_paths_timestamp_hash_same(const LookupPaths *lp, uint64_t timestamp_
return updated == timestamp_hash;
}
+static int directory_name_is_valid(const char *name) {
+ const char *suffix;
+
+ /* Accept a directory whose name is a valid unit file name ending in .wants/, .requires/ or .d/ */
+
+ FOREACH_STRING(suffix, ".wants", ".requires", ".d") {
+ _cleanup_free_ char *chopped = NULL;
+ const char *e;
+
+ e = endswith(name, suffix);
+ if (!e)
+ continue;
+
+ chopped = strndup(name, e - name);
+ if (!chopped)
+ return log_oom();
+
+ if (unit_name_is_valid(chopped, UNIT_NAME_ANY) ||
+ unit_type_from_string(chopped) >= 0)
+ return true;
+ }
+
+ return false;
+}
+
int unit_file_build_name_map(
const LookupPaths *lp,
uint64_t *cache_timestamp_hash,
@@ -287,50 +312,61 @@ 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;
/* We only care about valid units and dirs with certain suffixes, let's ignore the
* rest. */
- if (IN_SET(de->d_type, DT_REG, DT_LNK)) {
+ if (de->d_type == DT_REG) {
+ /* Accept a regular file whose name is a valid unit file name. */
if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY))
continue;
- /* Accept a regular file or symlink whose name is a valid unit file name. */
-
} else if (de->d_type == DT_DIR) {
- bool valid_dir_name = false;
- const char *suffix;
-
- /* Also accept a directory whose name is a valid unit file name ending in
- * .wants/, .requires/ or .d/ */
if (!paths) /* Skip directories early unless path_cache is requested */
continue;
- FOREACH_STRING(suffix, ".wants", ".requires", ".d") {
- _cleanup_free_ char *chopped = NULL;
- const char *e;
+ r = directory_name_is_valid(de->d_name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
+
+ } else if (de->d_type == DT_LNK) {
+
+ /* Accept a symlink file whose name is a valid unit file name or
+ * ending in .wants/, .requires/ or .d/. */
- e = endswith(de->d_name, suffix);
- if (!e)
+ if (!unit_name_is_valid(de->d_name, UNIT_NAME_ANY)) {
+ _cleanup_free_ char *target = NULL;
+
+ if (!paths) /* Skip symlink to a directory early unless path_cache is requested */
continue;
- chopped = strndup(de->d_name, e - de->d_name);
- if (!chopped)
- return log_oom();
+ r = directory_name_is_valid(de->d_name);
+ if (r < 0)
+ return r;
+ if (r == 0)
+ continue;
- if (unit_name_is_valid(chopped, UNIT_NAME_ANY) ||
- unit_type_from_string(chopped) >= 0) {
- valid_dir_name = true;
- break;
+ 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);
+ continue;
}
+
+ r = is_dir(target, /* follow = */ true);
+ if (r <= 0)
+ continue;
+
+ symlink_to_dir = true;
}
- if (!valid_dir_name)
- continue;
} else
continue;
@@ -347,9 +383,11 @@ int unit_file_build_name_map(
} else
_filename_free = filename; /* Make sure we free the filename. */
- if (!IN_SET(de->d_type, DT_REG, DT_LNK))
+ if (de->d_type == DT_DIR || (de->d_type == DT_LNK && symlink_to_dir))
continue;
+ assert(IN_SET(de->d_type, DT_REG, DT_LNK));
+
/* search_path is ordered by priority (highest first). If the name is already mapped
* to something (incl. itself), it means that we have already seen it, and we should
* ignore it here. */
diff --git a/test/units/testsuite-15.sh b/test/units/testsuite-15.sh
index 56ac1f774f..0446e71c38 100755
--- a/test/units/testsuite-15.sh
+++ b/test/units/testsuite-15.sh
@@ -515,6 +515,25 @@ test_invalid_dropins () {
return 0
}
+test_symlink_dropin_directory () {
+ # For issue #21920.
+ echo "Testing symlink drop-in directory..."
+ create_services test15-a
+ rmdir /{etc,run,usr/lib}/systemd/system/test15-a.service.d
+ mkdir -p /tmp/testsuite-15-test15-a-dropin-directory
+ ln -s /tmp/testsuite-15-test15-a-dropin-directory /etc/systemd/system/test15-a.service.d
+ cat >/tmp/testsuite-15-test15-a-dropin-directory/override.conf <<EOF
+[Unit]
+Description=hogehoge
+EOF
+ ln -s /tmp/testsuite-15-test15-a-dropin-directory-nonexistent /run/systemd/system/test15-a.service.d
+ touch /tmp/testsuite-15-test15-a-dropin-directory-regular
+ ln -s /tmp/testsuite-15-test15-a-dropin-directory-regular /usr/lib/systemd/system/test15-a.service.d
+ check_ok test15-a Description hogehoge
+
+ clear_services test15-a
+}
+
test_basic_dropins
test_linked_units
test_template_alias
@@ -523,5 +542,6 @@ test_template_dropins
test_alias_dropins
test_masked_dropins
test_invalid_dropins
+test_symlink_dropin_directory
touch /testok