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.
This commit is contained in:
Daan De Meyer 2023-04-13 19:03:43 +02:00 committed by Luca Boccassi
parent 6e4ec79a3c
commit e77e07f601
8 changed files with 100 additions and 43 deletions

View file

@ -62,23 +62,23 @@
<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>

View 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(

View 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);

View 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);

View 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);

View 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_;

View 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,

View 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) {