2aacef
From 201c651eaaf886064987272cc88b03995fd2715d Mon Sep 17 00:00:00 2001
2aacef
From: Mike Yuan <me@yhndnzj.com>
2aacef
Date: Fri, 18 Nov 2022 15:43:34 +0800
2aacef
Subject: [PATCH] systemctl: warn if trying to disable a unit with no install
2aacef
 info
2aacef
2aacef
Trying to disable a unit with no install info is mostly useless, so
2aacef
adding a warning like we do for enable (with the new dbus method
2aacef
'DisableUnitFilesWithFlagsAndInstallInfo()'). Note that it would
2aacef
still find and remove symlinks to the unit in /etc, regardless of
2aacef
whether it has install info or not, just like before. And if there are
2aacef
actually files to remove, we suppress the warning.
2aacef
2aacef
Fixes #17689
2aacef
2aacef
(cherry picked from commit bf1bea43f15b04152a3948702ba1695a0835c2bf)
2aacef
2aacef
Related: #2141979
2aacef
---
2aacef
 man/org.freedesktop.systemd1.xml | 13 ++++++++++++-
2aacef
 src/core/dbus-manager.c          | 21 ++++++++++++++++-----
2aacef
 src/shared/install.c             | 21 +++++++++++++++++----
2aacef
 src/systemctl/systemctl-enable.c | 15 ++++++++++-----
2aacef
 4 files changed, 55 insertions(+), 15 deletions(-)
2aacef
2aacef
diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml
2aacef
index 5e08b35234..c2f70870c7 100644
2aacef
--- a/man/org.freedesktop.systemd1.xml
2aacef
+++ b/man/org.freedesktop.systemd1.xml
2aacef
@@ -209,6 +209,10 @@ node /org/freedesktop/systemd1 {
2aacef
       DisableUnitFilesWithFlags(in  as files,
2aacef
                                 in  t flags,
2aacef
                                 out a(sss) changes);
2aacef
+      DisableUnitFilesWithFlagsAndInstallInfo(in  as files,
2aacef
+                                              in  t flags,
2aacef
+                                              out b carries_install_info,
2aacef
+                                              out a(sss) changes);
2aacef
       ReenableUnitFiles(in  as files,
2aacef
                         in  b runtime,
2aacef
                         in  b force,
2aacef
@@ -916,6 +920,8 @@ node /org/freedesktop/systemd1 {
2aacef
 
2aacef
     <variablelist class="dbus-method" generated="True" extra-ref="DisableUnitFilesWithFlags()"/>
2aacef
 
2aacef
+    <variablelist class="dbus-method" generated="True" extra-ref="DisableUnitFilesWithFlagsAndInstallInfo()"/>
2aacef
+
2aacef
     <variablelist class="dbus-method" generated="True" extra-ref="ReenableUnitFiles()"/>
2aacef
 
2aacef
     <variablelist class="dbus-method" generated="True" extra-ref="LinkUnitFiles()"/>
2aacef
@@ -1417,7 +1423,7 @@ node /org/freedesktop/systemd1 {
2aacef
       enabled for runtime only (true, <filename>/run/</filename>), or persistently (false,
2aacef
       <filename>/etc/</filename>). The second one controls whether symlinks pointing to other units shall be
2aacef
       replaced if necessary. This method returns one boolean and an array of the changes made. The boolean
2aacef
-      signals whether the unit files contained any enablement information (i.e. an [Install]) section. The
2aacef
+      signals whether the unit files contained any enablement information (i.e. an [Install] section). The
2aacef
       changes array consists of structures with three strings: the type of the change (one of
2aacef
       <literal>symlink</literal> or <literal>unlink</literal>), the file name of the symlink and the
2aacef
       destination of the symlink. Note that most of the following calls return a changes list in the same
2aacef
@@ -1440,6 +1446,11 @@ node /org/freedesktop/systemd1 {
2aacef
       replaced if necessary. <varname>SD_SYSTEMD_UNIT_PORTABLE</varname> will add or remove the symlinks in
2aacef
       <filename>/etc/systemd/system.attached</filename> and <filename>/run/systemd/system.attached</filename>.</para>
2aacef
 
2aacef
+      <para><function>DisableUnitFilesWithFlagsAndInstallInfo()</function> is similar to
2aacef
+      <function>DisableUnitFilesWithFlags()</function> and takes the same arguments, but returns
2aacef
+      a boolean to indicate whether the unit files contain any enablement information, like
2aacef
+      <function>EnableUnitFiles()</function>. The changes made are still returned in an array.</para>
2aacef
+
2aacef
       <para>Similarly, <function>ReenableUnitFiles()</function> applies the changes to one or more units that
2aacef
       would result from disabling and enabling the unit quickly one after the other in an atomic
2aacef
       fashion. This is useful to apply updated [Install] information contained in unit files.</para>
2aacef
diff --git a/src/core/dbus-manager.c b/src/core/dbus-manager.c
2aacef
index 88f098ec86..2db12721a1 100644
2aacef
--- a/src/core/dbus-manager.c
2aacef
+++ b/src/core/dbus-manager.c
2aacef
@@ -2425,6 +2425,7 @@ static int method_disable_unit_files_generic(
2aacef
                 sd_bus_message *message,
2aacef
                 Manager *m,
2aacef
                 int (*call)(LookupScope scope, UnitFileFlags flags, const char *root_dir, char *files[], InstallChange **changes, size_t *n_changes),
2aacef
+                bool carries_install_info,
2aacef
                 sd_bus_error *error) {
2aacef
 
2aacef
         _cleanup_strv_free_ char **l = NULL;
2aacef
@@ -2440,7 +2441,8 @@ static int method_disable_unit_files_generic(
2aacef
         if (r < 0)
2aacef
                 return r;
2aacef
 
2aacef
-        if (sd_bus_message_is_method_call(message, NULL, "DisableUnitFilesWithFlags")) {
2aacef
+        if (sd_bus_message_is_method_call(message, NULL, "DisableUnitFilesWithFlags") ||
2aacef
+            sd_bus_message_is_method_call(message, NULL, "DisableUnitFilesWithFlagsAndInstallInfo")) {
2aacef
                 uint64_t raw_flags;
2aacef
 
2aacef
                 r = sd_bus_message_read(message, "t", &raw_flags);
2aacef
@@ -2469,19 +2471,23 @@ static int method_disable_unit_files_generic(
2aacef
         if (r < 0)
2aacef
                 return install_error(error, r, changes, n_changes);
2aacef
 
2aacef
-        return reply_install_changes_and_free(m, message, -1, changes, n_changes, error);
2aacef
+        return reply_install_changes_and_free(m, message, carries_install_info ? r : -1, changes, n_changes, error);
2aacef
 }
2aacef
 
2aacef
 static int method_disable_unit_files_with_flags(sd_bus_message *message, void *userdata, sd_bus_error *error) {
2aacef
-        return method_disable_unit_files_generic(message, userdata, unit_file_disable, error);
2aacef
+        return method_disable_unit_files_generic(message, userdata, unit_file_disable, /* carries_install_info = */ false, error);
2aacef
+}
2aacef
+
2aacef
+static int method_disable_unit_files_with_flags_and_install_info(sd_bus_message *message, void *userdata, sd_bus_error *error) {
2aacef
+        return method_disable_unit_files_generic(message, userdata, unit_file_disable, /* carries_install_info = */ true, error);
2aacef
 }
2aacef
 
2aacef
 static int method_disable_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) {
2aacef
-        return method_disable_unit_files_generic(message, userdata, unit_file_disable, error);
2aacef
+        return method_disable_unit_files_generic(message, userdata, unit_file_disable, /* carries_install_info = */ false, error);
2aacef
 }
2aacef
 
2aacef
 static int method_unmask_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) {
2aacef
-        return method_disable_unit_files_generic(message, userdata, unit_file_unmask, error);
2aacef
+        return method_disable_unit_files_generic(message, userdata, unit_file_unmask, /* carries_install_info = */ false, error);
2aacef
 }
2aacef
 
2aacef
 static int method_revert_unit_files(sd_bus_message *message, void *userdata, sd_bus_error *error) {
2aacef
@@ -3194,6 +3200,11 @@ const sd_bus_vtable bus_manager_vtable[] = {
2aacef
                                 SD_BUS_RESULT("a(sss)", changes),
2aacef
                                 method_disable_unit_files_with_flags,
2aacef
                                 SD_BUS_VTABLE_UNPRIVILEGED),
2aacef
+        SD_BUS_METHOD_WITH_ARGS("DisableUnitFilesWithFlagsAndInstallInfo",
2aacef
+                                SD_BUS_ARGS("as", files, "t", flags),
2aacef
+                                SD_BUS_RESULT("b", carries_install_info, "a(sss)", changes),
2aacef
+                                method_disable_unit_files_with_flags_and_install_info,
2aacef
+                                SD_BUS_VTABLE_UNPRIVILEGED),
2aacef
         SD_BUS_METHOD_WITH_ARGS("ReenableUnitFiles",
2aacef
                                 SD_BUS_ARGS("as", files, "b", runtime, "b", force),
2aacef
                                 SD_BUS_RESULT("b", carries_install_info, "a(sss)", changes),
2aacef
diff --git a/src/shared/install.c b/src/shared/install.c
2aacef
index 834a1c59e3..4b610b20a5 100644
2aacef
--- a/src/shared/install.c
2aacef
+++ b/src/shared/install.c
2aacef
@@ -2790,25 +2790,38 @@ static int do_unit_file_disable(
2aacef
 
2aacef
         _cleanup_(install_context_done) InstallContext ctx = { .scope = scope };
2aacef
         _cleanup_set_free_free_ Set *remove_symlinks_to = NULL;
2aacef
+        InstallInfo *info;
2aacef
+        bool has_install_info = false;
2aacef
         int r;
2aacef
 
2aacef
         STRV_FOREACH(name, names) {
2aacef
                 if (!unit_name_is_valid(*name, UNIT_NAME_ANY))
2aacef
                         return install_changes_add(changes, n_changes, -EUCLEAN, *name, NULL);
2aacef
 
2aacef
-                r = install_info_add(&ctx, *name, NULL, lp->root_dir, /* auxiliary= */ false, NULL);
2aacef
+                r = install_info_add(&ctx, *name, NULL, lp->root_dir, /* auxiliary= */ false, &info;;
2aacef
+                if (r >= 0)
2aacef
+                        r = install_info_traverse(&ctx, lp, info, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS, NULL);
2aacef
+
2aacef
                 if (r < 0)
2aacef
-                        return r;
2aacef
+                        return install_changes_add(changes, n_changes, r, *name, NULL);
2aacef
+
2aacef
+                /* If we enable multiple units, some with install info and others without,
2aacef
+                 * the "empty [Install] section" warning is not shown. Let's make the behavior
2aacef
+                 * of disable align with that. */
2aacef
+                has_install_info = has_install_info || install_info_has_rules(info) || install_info_has_also(info);
2aacef
         }
2aacef
 
2aacef
         r = install_context_mark_for_removal(&ctx, lp, &remove_symlinks_to, config_path, changes, n_changes);
2aacef
+        if (r >= 0)
2aacef
+                r = remove_marked_symlinks(remove_symlinks_to, config_path, lp, flags & UNIT_FILE_DRY_RUN, changes, n_changes);
2aacef
+
2aacef
         if (r < 0)
2aacef
                 return r;
2aacef
 
2aacef
-        return remove_marked_symlinks(remove_symlinks_to, config_path, lp, flags & UNIT_FILE_DRY_RUN, changes, n_changes);
2aacef
+        /* The warning is shown only if it's a no-op */
2aacef
+        return install_changes_have_modification(*changes, *n_changes) || has_install_info;
2aacef
 }
2aacef
 
2aacef
-
2aacef
 int unit_file_disable(
2aacef
                 LookupScope scope,
2aacef
                 UnitFileFlags flags,
2aacef
diff --git a/src/systemctl/systemctl-enable.c b/src/systemctl/systemctl-enable.c
2aacef
index 5be4c0c725..aea66872de 100644
2aacef
--- a/src/systemctl/systemctl-enable.c
2aacef
+++ b/src/systemctl/systemctl-enable.c
2aacef
@@ -109,9 +109,10 @@ int verb_enable(int argc, char *argv[], void *userdata) {
2aacef
                 if (streq(verb, "enable")) {
2aacef
                         r = unit_file_enable(arg_scope, flags, arg_root, names, &changes, &n_changes);
2aacef
                         carries_install_info = r;
2aacef
-                } else if (streq(verb, "disable"))
2aacef
+                } else if (streq(verb, "disable")) {
2aacef
                         r = unit_file_disable(arg_scope, flags, arg_root, names, &changes, &n_changes);
2aacef
-                else if (streq(verb, "reenable")) {
2aacef
+                        carries_install_info = r;
2aacef
+                } else if (streq(verb, "reenable")) {
2aacef
                         r = unit_file_reenable(arg_scope, flags, arg_root, names, &changes, &n_changes);
2aacef
                         carries_install_info = r;
2aacef
                 } else if (streq(verb, "link"))
2aacef
@@ -165,7 +166,8 @@ int verb_enable(int argc, char *argv[], void *userdata) {
2aacef
                         method = "EnableUnitFiles";
2aacef
                         expect_carries_install_info = true;
2aacef
                 } else if (streq(verb, "disable")) {
2aacef
-                        method = "DisableUnitFiles";
2aacef
+                        method = "DisableUnitFilesWithFlagsAndInstallInfo";
2aacef
+                        expect_carries_install_info = true;
2aacef
                         send_force = false;
2aacef
                 } else if (streq(verb, "reenable")) {
2aacef
                         method = "ReenableUnitFiles";
2aacef
@@ -208,7 +210,10 @@ int verb_enable(int argc, char *argv[], void *userdata) {
2aacef
                 }
2aacef
 
2aacef
                 if (send_runtime) {
2aacef
-                        r = sd_bus_message_append(m, "b", arg_runtime);
2aacef
+                        if (streq(method, "DisableUnitFilesWithFlagsAndInstallInfo"))
2aacef
+                                r = sd_bus_message_append(m, "t", arg_runtime ? UNIT_FILE_RUNTIME : 0);
2aacef
+                        else
2aacef
+                                r = sd_bus_message_append(m, "b", arg_runtime);
2aacef
                         if (r < 0)
2aacef
                                 return bus_log_create_error(r);
2aacef
                 }
2aacef
@@ -245,7 +250,7 @@ int verb_enable(int argc, char *argv[], void *userdata) {
2aacef
         if (carries_install_info == 0 && !ignore_carries_install_info)
2aacef
                 log_notice("The unit files have no installation config (WantedBy=, RequiredBy=, Also=,\n"
2aacef
                            "Alias= settings in the [Install] section, and DefaultInstance= for template\n"
2aacef
-                           "units). This means they are not meant to be enabled using systemctl.\n"
2aacef
+                           "units). This means they are not meant to be enabled or disabled using systemctl.\n"
2aacef
                            " \n" /* trick: the space is needed so that the line does not get stripped from output */
2aacef
                            "Possible reasons for having this kind of units are:\n"
2aacef
                            "%1$s A unit may be statically enabled by being symlinked from another unit's\n"