]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
preset: Add ignore directive
authorDaan De Meyer <daan.j.demeyer@gmail.com>
Thu, 13 Apr 2023 17:03:43 +0000 (19:03 +0200)
committerLuca Boccassi <luca.boccassi@gmail.com>
Fri, 14 Apr 2023 19:27:59 +0000 (20:27 +0100)
The ignore directive specifies to not do anything with the given
unit and leave existing configuration intact. This allows distributions
to gradually adopt preset files by shipping a ignore * preset file.

man/systemd.preset.xml
src/core/dbus-unit.c
src/core/unit.c
src/core/unit.h
src/shared/install.c
src/shared/install.h
src/systemctl/systemctl-list-unit-files.c
src/test/test-install-root.c

index ab730d2cc21504a9033b5675e3da6dfac4d29e7a..5d46a88c3e94f41c158727d3f2e6c8260ea01c64 100644 (file)
   <refsect1>
     <title>Preset File Format</title>
 
-    <para>The preset files contain a list of directives consisting of
-    either the word <literal>enable</literal> or
-    <literal>disable</literal> followed by a space and a unit name
-    (possibly with shell style wildcards), separated by newlines.
-    Empty lines and lines whose first non-whitespace character is <literal>#</literal> or
-    <literal>;</literal> are ignored. Multiple instance names for unit
-    templates may be specified as a space separated list at the end of
-    the line instead of the customary position between <literal>@</literal>
-    and the unit suffix.</para>
+    <para>The preset files contain a list of directives, one per line. Empty lines and lines whose first
+    non-whitespace character is <literal>#</literal> or <literal>;</literal> are ignored.  Each directive
+    consists of one of the words <literal>enable</literal>, <literal>disable</literal>, or
+    <literal>ignore</literal>, followed by whitespace and a unit name. The unit name may contain shell-style
+    wildcards.</para>
+
+    <para>For the enable directive for template units, one or more instance names may be specified as a
+    space-separated list after the unit name. In this case, those instances will be enabled instead of the
+    instance specified via DefaultInstance= in the unit.</para>
 
     <para>Presets must refer to the "real" unit file, and not to any aliases. See
     <citerefentry><refentrytitle>systemd.unit</refentrytitle><manvolnum>5</manvolnum></citerefentry>
     for a description of unit aliasing.</para>
 
-    <para>Two different directives are understood:
-    <literal>enable</literal> may be used to enable units by default,
-    <literal>disable</literal> to disable units by default.</para>
+    <para>Three different directives are understood: <literal>enable</literal> may be used to enable units by
+    default, <literal>disable</literal> to disable units by default, and <literal>ignore</literal> to ignore
+    units and leave existing configuration intact.</para>
 
     <para>If multiple lines apply to a unit name, the first matching
     one takes precedence over all others.</para>
index c01f41cb449766f6245a02ce2fa6bbdaeff248b4..dd36cae860d8a8763c9b1f88657a48fc176eb7ef 100644 (file)
@@ -235,9 +235,7 @@ static int property_get_unit_file_preset(
 
         r = unit_get_unit_file_preset(u);
 
-        return sd_bus_message_append(reply, "s",
-                                     r < 0 ? NULL:
-                                     r > 0 ? "enabled" : "disabled");
+        return sd_bus_message_append(reply, "s", preset_action_past_tense_to_string(r));
 }
 
 static int property_get_job(
index 298a6de7b339ef1cff610bec5fe8fe493fd9e843..76d823aed07374a6b656729f8e846daf5b550b15 100644 (file)
@@ -4094,7 +4094,7 @@ UnitFileState unit_get_unit_file_state(Unit *u) {
         return u->unit_file_state;
 }
 
-int unit_get_unit_file_preset(Unit *u) {
+PresetAction unit_get_unit_file_preset(Unit *u) {
         int r;
 
         assert(u);
index d3eb5ba881eb068d098c45e314b101107450dcf6..ea236d933cf58e643668daa4a10f858d0ad64a06 100644 (file)
@@ -11,6 +11,7 @@
 #include "bpf-program.h"
 #include "condition.h"
 #include "emergency-action.h"
+#include "install.h"
 #include "list.h"
 #include "show-status.h"
 #include "set.h"
@@ -358,7 +359,7 @@ typedef struct Unit {
 
         /* Cached unit file state and preset */
         UnitFileState unit_file_state;
-        int unit_file_preset;
+        PresetAction unit_file_preset;
 
         /* Where the cpu.stat or cpuacct.usage was at the time the unit was started */
         nsec_t cpu_usage_base;
@@ -954,7 +955,7 @@ void unit_start_on_failure(Unit *u, const char *dependency_name, UnitDependencyA
 void unit_trigger_notify(Unit *u);
 
 UnitFileState unit_get_unit_file_state(Unit *u);
-int unit_get_unit_file_preset(Unit *u);
+PresetAction unit_get_unit_file_preset(Unit *u);
 
 Unit* unit_ref_set(UnitRef *ref, Unit *source, Unit *target);
 void unit_ref_unset(UnitRef *ref);
index eddc41964ac3087fed6062963eaf58eaa6e9cb9f..152e517ebc155fa4ef64ab898f5d22e5953442a5 100644 (file)
@@ -52,18 +52,22 @@ typedef struct {
         OrderedHashmap *have_processed;
 } InstallContext;
 
-typedef enum {
-        PRESET_UNKNOWN,
-        PRESET_ENABLE,
-        PRESET_DISABLE,
-} PresetAction;
-
 struct UnitFilePresetRule {
         char *pattern;
         PresetAction action;
         char **instances;
 };
 
+/* NB! strings use past tense. */
+static const char *const preset_action_past_tense_table[_PRESET_ACTION_MAX] = {
+        [PRESET_UNKNOWN] = "unknown",
+        [PRESET_ENABLE]  = "enabled",
+        [PRESET_DISABLE] = "disabled",
+        [PRESET_IGNORE]  = "ignored",
+};
+
+DEFINE_STRING_TABLE_LOOKUP_TO_STRING(preset_action_past_tense, PresetAction);
+
 static bool install_info_has_rules(const InstallInfo *i) {
         assert(i);
 
@@ -3297,6 +3301,20 @@ static int read_presets(RuntimeScope scope, const char *root_dir, UnitFilePreset
                                 };
                         }
 
+                        parameter = first_word(l, "ignore");
+                        if (parameter) {
+                                char *pattern;
+
+                                pattern = strdup(parameter);
+                                if (!pattern)
+                                        return -ENOMEM;
+
+                                rule = (UnitFilePresetRule) {
+                                        .pattern = pattern,
+                                        .action = PRESET_IGNORE,
+                                };
+                        }
+
                         if (rule.action) {
                                 if (!GREEDY_REALLOC(ps.rules, ps.n_rules + 1))
                                         return -ENOMEM;
@@ -3382,23 +3400,26 @@ static int query_presets(const char *name, const UnitFilePresets *presets, char
         switch (action) {
         case PRESET_UNKNOWN:
                 log_debug("Preset files don't specify rule for %s. Enabling.", name);
-                return 1;
+                return PRESET_ENABLE;
         case PRESET_ENABLE:
                 if (instance_name_list && *instance_name_list)
                         STRV_FOREACH(s, *instance_name_list)
                                 log_debug("Preset files say enable %s.", *s);
                 else
                         log_debug("Preset files say enable %s.", name);
-                return 1;
+                return PRESET_ENABLE;
         case PRESET_DISABLE:
                 log_debug("Preset files say disable %s.", name);
-                return 0;
+                return PRESET_DISABLE;
+        case PRESET_IGNORE:
+                log_debug("Preset files say ignore %s.", name);
+                return PRESET_IGNORE;
         default:
                 assert_not_reached();
         }
 }
 
-int unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached) {
+PresetAction unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached) {
         _cleanup_(unit_file_presets_done) UnitFilePresets tmp = {};
         int r;
 
@@ -3492,7 +3513,7 @@ static int preset_prepare_one(
         if (r < 0)
                 return r;
 
-        if (r > 0) {
+        if (r == PRESET_ENABLE) {
                 if (instance_name_list)
                         STRV_FOREACH(s, instance_name_list) {
                                 r = install_info_discover_and_check(plus, lp, *s, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
@@ -3507,7 +3528,7 @@ static int preset_prepare_one(
                                 return r;
                 }
 
-        } else
+        } else if (r == PRESET_DISABLE)
                 r = install_info_discover(minus, lp, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
                                           &info, changes, n_changes);
 
index 9b27a046b9c31402ee2653dc76af948197948a40..30b07a725fe028ce5be9cfcffca11110a6473e58 100644 (file)
@@ -217,8 +217,20 @@ typedef struct {
         bool initialized;
 } UnitFilePresets;
 
+typedef enum PresetAction {
+        PRESET_UNKNOWN,
+        PRESET_ENABLE,
+        PRESET_DISABLE,
+        PRESET_IGNORE,
+        _PRESET_ACTION_MAX,
+        _PRESET_ACTION_INVALID = -EINVAL,
+        _PRESET_ACTION_ERRNO_MAX = -ERRNO_MAX, /* Ensure this type covers the whole negative errno range */
+} PresetAction;
+
+const char *preset_action_past_tense_to_string(PresetAction action);
+
 void unit_file_presets_done(UnitFilePresets *p);
-int unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached);
+PresetAction unit_file_query_preset(RuntimeScope scope, const char *root_dir, const char *name, UnitFilePresets *cached);
 
 const char *unit_file_state_to_string(UnitFileState s) _const_;
 UnitFileState unit_file_state_from_string(const char *s) _pure_;
index 4b15e1ca6c3fc5f7f88042266285b22ab5516dae..aad248fe1f56989b0a2406cf23cc2609b0534742 100644 (file)
@@ -49,6 +49,21 @@ static bool output_show_unit_file(const UnitFileList *u, char **states, char **p
         return true;
 }
 
+static const char* preset_action_to_color(PresetAction action, bool underline) {
+        assert(action >= 0);
+
+        switch (action) {
+        case PRESET_ENABLE:
+                return underline ? ansi_highlight_green_underline() : ansi_highlight_green();
+        case PRESET_DISABLE:
+                return underline ? ansi_highlight_red_underline() : ansi_highlight_red();
+        case PRESET_IGNORE:
+                return underline ? ansi_highlight_yellow_underline() : ansi_highlight_yellow();
+        default:
+                return NULL;
+        }
+}
+
 static int output_unit_file_list(const UnitFileList *units, unsigned c) {
         _cleanup_(table_unrefp) Table *table = NULL;
         _cleanup_(unit_file_presets_done) UnitFilePresets presets = {};
@@ -98,22 +113,14 @@ static int output_unit_file_list(const UnitFileList *units, unsigned c) {
                         return table_log_add_error(r);
 
                 if (show_preset_for_state(u->state)) {
-                        const char *unit_preset_str, *on_preset_color;
+                        const char *on_preset_color = underline ? on_underline : ansi_normal();
 
                         r = unit_file_query_preset(arg_runtime_scope, arg_root, id, &presets);
-                        if (r < 0) {
-                                unit_preset_str = "n/a";
-                                on_preset_color = underline ? on_underline : ansi_normal();
-                        } else if (r == 0) {
-                                unit_preset_str = "disabled";
-                                on_preset_color = underline ? ansi_highlight_red_underline() : ansi_highlight_red();
-                        } else {
-                                unit_preset_str = "enabled";
-                                on_preset_color = underline ? ansi_highlight_green_underline() : ansi_highlight_green();
-                        }
+                        if (r >= 0)
+                                on_preset_color = preset_action_to_color(r, underline);
 
                         r = table_add_many(table,
-                                           TABLE_STRING, unit_preset_str,
+                                           TABLE_STRING, strna(preset_action_past_tense_to_string(r)),
                                            TABLE_SET_BOTH_COLORS, strempty(on_preset_color));
                 } else
                         r = table_add_many(table,
index c866cff022a1730628d83a0c7d96c325d3cf91dd..55b8894ecc1651f686a31241067e0541067c952a 100644 (file)
@@ -580,8 +580,11 @@ TEST(preset_and_list) {
         UnitFileList *fl;
         _cleanup_(hashmap_freep) Hashmap *h = NULL;
 
+        CLEANUP_ARRAY(changes, n_changes, install_changes_free);
+
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) == -ENOENT);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) == -ENOENT);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) == -ENOENT);
 
         p = strjoina(root, "/usr/lib/systemd/system/preset-yes.service");
         assert_se(write_string_file(p,
@@ -593,13 +596,20 @@ TEST(preset_and_list) {
                                     "[Install]\n"
                                     "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0);
 
+        p = strjoina(root, "/usr/lib/systemd/system/preset-ignore.service");
+        assert_se(write_string_file(p,
+                                    "[Install]\n"
+                                    "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0);
+
         p = strjoina(root, "/usr/lib/systemd/system-preset/test.preset");
         assert_se(write_string_file(p,
                                     "enable *-yes.*\n"
+                                    "ignore *-ignore.*\n"
                                     "disable *\n", WRITE_STRING_FILE_CREATE) >= 0);
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
 
         assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
         assert_se(n_changes == 1);
@@ -612,6 +622,7 @@ TEST(preset_and_list) {
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
 
         assert_se(unit_file_disable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-yes.service"), &changes, &n_changes) >= 0);
         assert_se(n_changes == 1);
@@ -623,6 +634,7 @@ TEST(preset_and_list) {
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
 
         assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-no.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
         assert_se(n_changes == 0);
@@ -631,6 +643,7 @@ TEST(preset_and_list) {
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
 
         assert_se(unit_file_preset_all(RUNTIME_SCOPE_SYSTEM, 0, root, UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
 
@@ -652,6 +665,7 @@ TEST(preset_and_list) {
 
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-yes.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
         assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-no.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
 
         assert_se(h = hashmap_new(&unit_file_list_hash_ops_free));
         assert_se(unit_file_get_list(RUNTIME_SCOPE_SYSTEM, root, h, NULL, NULL) >= 0);
@@ -674,6 +688,10 @@ TEST(preset_and_list) {
         }
 
         assert_se(got_yes && got_no);
+
+        assert_se(unit_file_enable(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-ignore.service"), &changes, &n_changes) >= 0);
+        assert_se(unit_file_preset(RUNTIME_SCOPE_SYSTEM, 0, root, STRV_MAKE("preset-ignore.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
+        assert_se(unit_file_get_state(RUNTIME_SCOPE_SYSTEM, root, "preset-ignore.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
 }
 
 TEST(revert) {