From f88f7c68264f9cfef78f4a4e2f68e45de8f1f055 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= Date: Tue, 15 Mar 2022 09:44:39 +0100 Subject: [PATCH] shared/install: fix reenable on linked unit files (cherry picked from commit 29a7c59abbe594422f1ed7602263420745339a3e) Related: #2082131 --- src/shared/install.c | 73 ++++++++++++++++++++++++++++++----- src/shared/install.h | 2 +- test/test-systemctl-enable.sh | 8 ++-- 3 files changed, 68 insertions(+), 15 deletions(-) diff --git a/src/shared/install.c b/src/shared/install.c index 1018e4fbf3..bf11e5bdce 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -2707,17 +2707,74 @@ int unit_file_disable( return do_unit_file_disable(&lp, scope, flags, config_path, files, changes, n_changes); } +static int normalize_linked_files( + UnitFileScope scope, + const LookupPaths *lp, + char **names_or_paths, + char ***ret_names, + char ***ret_files) { + + /* This is similar to normalize_filenames()/normalize_names() in src/systemctl/, + * but operates on real unit names. For each argument we we look up the actual path + * where the unit is found. This way linked units can be reenabled successfully. */ + + _cleanup_free_ char **files = NULL, **names = NULL; + int r; + + STRV_FOREACH(a, names_or_paths) { + _cleanup_(install_context_done) InstallContext ctx = { .scope = scope }; + UnitFileInstallInfo *i = NULL; + _cleanup_free_ char *n = NULL; + + r = path_extract_filename(*a, &n); + if (r < 0) + return r; + if (r == O_DIRECTORY) + return log_debug_errno(SYNTHETIC_ERRNO(EISDIR), + "Unexpected path to a directory \"%s\", refusing.", *a); + + if (!is_path(*a)) { + r = install_info_discover(&ctx, lp, n, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, &i, NULL, NULL); + if (r < 0) + log_debug_errno(r, "Failed to discover unit \"%s\", operating on name: %m", n); + } + + r = strv_consume(&names, TAKE_PTR(n)); + if (r < 0) + return r; + + const char *p = NULL; + if (i && i->path) + /* Use startswith here, because we know that paths are normalized, and + * path_startswith() would give us a relative path, but we need an absolute path + * relative to i->root. + * + * In other words: /var/tmp/instroot.1234/etc/systemd/system/frobnicator.service + * is replaced by /etc/systemd/system/frobnicator.service, which is "absolute" + * in a sense, but only makes sense "relative" to /var/tmp/instroot.1234/. + */ + p = startswith(i->path, i->root); + + r = strv_extend(&files, p ?: *a); + if (r < 0) + return r; + } + + *ret_names = TAKE_PTR(names); + *ret_files = TAKE_PTR(files); + return 0; +} + int unit_file_reenable( UnitFileScope scope, UnitFileFlags flags, const char *root_dir, - char **files, + char **names_or_paths, UnitFileChange **changes, size_t *n_changes) { _cleanup_(lookup_paths_free) LookupPaths lp = {}; - size_t l, i; - char **names; + _cleanup_strv_free_ char **names = NULL, **files = NULL; int r; assert(scope >= 0); @@ -2731,13 +2788,11 @@ int unit_file_reenable( if (!config_path) return -ENXIO; - /* First, we invoke the disable command with only the basename... */ - l = strv_length(files); - names = newa(char*, l+1); - for (i = 0; i < l; i++) - names[i] = basename(files[i]); - names[i] = NULL; + r = normalize_linked_files(scope, &lp, names_or_paths, &names, &files); + if (r < 0) + return r; + /* First, we invoke the disable command with only the basename... */ r = do_unit_file_disable(&lp, scope, flags, config_path, names, changes, n_changes); if (r < 0) return r; diff --git a/src/shared/install.h b/src/shared/install.h index d21e2aaa45..dba6987406 100644 --- a/src/shared/install.h +++ b/src/shared/install.h @@ -111,7 +111,7 @@ int unit_file_reenable( UnitFileScope scope, UnitFileFlags flags, const char *root_dir, - char **files, + char **names_or_paths, UnitFileChange **changes, size_t *n_changes); int unit_file_preset( diff --git a/test/test-systemctl-enable.sh b/test/test-systemctl-enable.sh index c1fb9626ab..0ed08a9da3 100644 --- a/test/test-systemctl-enable.sh +++ b/test/test-systemctl-enable.sh @@ -206,11 +206,9 @@ test ! -h "$root/etc/systemd/system/paths.target.wants/link1.path" islink "$root/etc/systemd/system/link1.path" "/link1.path" islink "$root/etc/systemd/system/paths.target.wants/link1.path" "/link1.path" -# FIXME -# "$systemctl" --root="$root" reenable 'link1.path' -# islink "$root/etc/systemd/system/link1.path" "/link1.path" -# islink "$root/etc/systemd/system/paths.target.wants/link1.path" "/link1.path" - +"$systemctl" --root="$root" reenable 'link1.path' +islink "$root/etc/systemd/system/link1.path" "/link1.path" +islink "$root/etc/systemd/system/paths.target.wants/link1.path" "/link1.path" : -------manual link------------------------------------------ cat >"$root/link3.suffix" <