From e77e07f60186b96a9b5df398e92a18a72918e0f2 Mon Sep 17 00:00:00 2001 From: Daan De Meyer Date: Thu, 13 Apr 2023 19:03:43 +0200 Subject: [PATCH] preset: Add ignore directive 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 | 24 ++++++------ src/core/dbus-unit.c | 4 +- src/core/unit.c | 2 +- src/core/unit.h | 5 ++- src/shared/install.c | 45 +++++++++++++++++------ src/shared/install.h | 14 ++++++- src/systemctl/systemctl-list-unit-files.c | 31 ++++++++++------ src/test/test-install-root.c | 18 +++++++++ 8 files changed, 100 insertions(+), 43 deletions(-) diff --git a/man/systemd.preset.xml b/man/systemd.preset.xml index ab730d2cc21..5d46a88c3e9 100644 --- a/man/systemd.preset.xml +++ b/man/systemd.preset.xml @@ -62,23 +62,23 @@ Preset File Format - The preset files contain a list of directives consisting of - either the word enable or - disable 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 # or - ; 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 @ - and the unit suffix. + The preset files contain a list of directives, one per line. Empty lines and lines whose first + non-whitespace character is # or ; are ignored. Each directive + consists of one of the words enable, disable, or + ignore, followed by whitespace and a unit name. The unit name may contain shell-style + wildcards. + + 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. Presets must refer to the "real" unit file, and not to any aliases. See systemd.unit5 for a description of unit aliasing. - Two different directives are understood: - enable may be used to enable units by default, - disable to disable units by default. + Three different directives are understood: enable may be used to enable units by + default, disable to disable units by default, and ignore to ignore + units and leave existing configuration intact. If multiple lines apply to a unit name, the first matching one takes precedence over all others. diff --git a/src/core/dbus-unit.c b/src/core/dbus-unit.c index c01f41cb449..dd36cae860d 100644 --- a/src/core/dbus-unit.c +++ b/src/core/dbus-unit.c @@ -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( diff --git a/src/core/unit.c b/src/core/unit.c index 298a6de7b33..76d823aed07 100644 --- a/src/core/unit.c +++ b/src/core/unit.c @@ -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); diff --git a/src/core/unit.h b/src/core/unit.h index d3eb5ba881e..ea236d933cf 100644 --- a/src/core/unit.h +++ b/src/core/unit.h @@ -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); diff --git a/src/shared/install.c b/src/shared/install.c index eddc41964ac..152e517ebc1 100644 --- a/src/shared/install.c +++ b/src/shared/install.c @@ -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); diff --git a/src/shared/install.h b/src/shared/install.h index 9b27a046b9c..30b07a725fe 100644 --- a/src/shared/install.h +++ b/src/shared/install.h @@ -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_; diff --git a/src/systemctl/systemctl-list-unit-files.c b/src/systemctl/systemctl-list-unit-files.c index 4b15e1ca6c3..aad248fe1f5 100644 --- a/src/systemctl/systemctl-list-unit-files.c +++ b/src/systemctl/systemctl-list-unit-files.c @@ -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, diff --git a/src/test/test-install-root.c b/src/test/test-install-root.c index c866cff022a..55b8894ecc1 100644 --- a/src/test/test-install-root.c +++ b/src/test/test-install-root.c @@ -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) {