bd1529
From 4c41ad9418058aefb2d2732b0b65da9c7cdf5151 Mon Sep 17 00:00:00 2001
bd1529
From: Ruixin Bao <rubao@redhat.com>
bd1529
Date: Tue, 21 Aug 2018 20:40:56 +0000
bd1529
Subject: [PATCH] install: allow instantiated units to be enabled via presets
bd1529
bd1529
This patch implements https://github.com/systemd/systemd/issues/9421.
bd1529
bd1529
The .preset file now is able to take a rule in the format of:(e.g)
bd1529
enable foo@.service bar0 bar1 bar2
bd1529
bd1529
In the above example, when preset-all is called, all three instances of
bd1529
foo@bar0.service, foo@bar1.service and foo@bar2.service will be enabled.
bd1529
bd1529
When preset is called on a single service(e.g: foo@bar1.service), only
bd1529
the mentioned one(foo@bar1.service) will be enabled.
bd1529
bd1529
Tests are added for future regression.
bd1529
bd1529
(cherry picked from commit 4c9565eea534cd233a913c8c21f7920dba229743)
bd1529
bd1529
Resolves: #1812972
bd1529
---
bd1529
 src/shared/install.c         | 155 ++++++++++++++++++++++++++++++-----
bd1529
 src/test/test-install-root.c |  57 +++++++++++++
bd1529
 2 files changed, 193 insertions(+), 19 deletions(-)
bd1529
bd1529
diff --git a/src/shared/install.c b/src/shared/install.c
bd1529
index 77ae812878..1d4beaa83b 100644
bd1529
--- a/src/shared/install.c
bd1529
+++ b/src/shared/install.c
bd1529
@@ -60,6 +60,7 @@ typedef enum {
bd1529
 typedef struct {
bd1529
         char *pattern;
bd1529
         PresetAction action;
bd1529
+        char **instances;
bd1529
 } PresetRule;
bd1529
 
bd1529
 typedef struct {
bd1529
@@ -87,8 +88,10 @@ static inline void presets_freep(Presets *p) {
bd1529
         if (!p)
bd1529
                 return;
bd1529
 
bd1529
-        for (i = 0; i < p->n_rules; i++)
bd1529
+        for (i = 0; i < p->n_rules; i++) {
bd1529
                 free(p->rules[i].pattern);
bd1529
+                strv_free(p->rules[i].instances);
bd1529
+        }
bd1529
 
bd1529
         free(p->rules);
bd1529
         p->n_rules = 0;
bd1529
@@ -2755,6 +2758,39 @@ int unit_file_exists(UnitFileScope scope, const LookupPaths *paths, const char *
bd1529
         return 1;
bd1529
 }
bd1529
 
bd1529
+static int split_pattern_into_name_and_instances(const char *pattern, char **out_unit_name, char ***out_instances) {
bd1529
+        _cleanup_strv_free_ char **instances = NULL;
bd1529
+        _cleanup_free_ char *unit_name = NULL;
bd1529
+        int r;
bd1529
+
bd1529
+        assert(pattern);
bd1529
+        assert(out_instances);
bd1529
+        assert(out_unit_name);
bd1529
+
bd1529
+        r = extract_first_word(&pattern, &unit_name, NULL, 0);
bd1529
+        if (r < 0)
bd1529
+                return r;
bd1529
+
bd1529
+        /* We handle the instances logic when unit name is extracted */
bd1529
+        if (pattern) {
bd1529
+                /* We only create instances when a rule of templated unit
bd1529
+                 * is seen. A rule like enable foo@.service a b c will
bd1529
+                 * result in an array of (a, b, c) as instance names */
bd1529
+                if (!unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE))
bd1529
+                        return -EINVAL;
bd1529
+
bd1529
+                instances = strv_split(pattern, WHITESPACE);
bd1529
+                if (!instances)
bd1529
+                        return -ENOMEM;
bd1529
+
bd1529
+                *out_instances = TAKE_PTR(instances);
bd1529
+        }
bd1529
+
bd1529
+        *out_unit_name = TAKE_PTR(unit_name);
bd1529
+
bd1529
+        return 0;
bd1529
+}
bd1529
+
bd1529
 static int read_presets(UnitFileScope scope, const char *root_dir, Presets *presets) {
bd1529
         _cleanup_(presets_freep) Presets ps = {};
bd1529
         size_t n_allocated = 0;
bd1529
@@ -2824,15 +2860,20 @@ static int read_presets(UnitFileScope scope, const char *root_dir, Presets *pres
bd1529
 
bd1529
                         parameter = first_word(l, "enable");
bd1529
                         if (parameter) {
bd1529
-                                char *pattern;
bd1529
+                                char *unit_name;
bd1529
+                                char **instances = NULL;
bd1529
 
bd1529
-                                pattern = strdup(parameter);
bd1529
-                                if (!pattern)
bd1529
-                                        return -ENOMEM;
bd1529
+                                /* Unit_name will remain the same as parameter when no instances are specified */
bd1529
+                                r = split_pattern_into_name_and_instances(parameter, &unit_name, &instances);
bd1529
+                                if (r < 0) {
bd1529
+                                        log_syntax(NULL, LOG_WARNING, *p, n, 0, "Couldn't parse line '%s'. Ignoring.", line);
bd1529
+                                        continue;
bd1529
+                                }
bd1529
 
bd1529
                                 rule = (PresetRule) {
bd1529
-                                        .pattern = pattern,
bd1529
+                                        .pattern = unit_name,
bd1529
                                         .action = PRESET_ENABLE,
bd1529
+                                        .instances = instances,
bd1529
                                 };
bd1529
                         }
bd1529
 
bd1529
@@ -2868,15 +2909,71 @@ static int read_presets(UnitFileScope scope, const char *root_dir, Presets *pres
bd1529
         return 0;
bd1529
 }
bd1529
 
bd1529
-static int query_presets(const char *name, const Presets presets) {
bd1529
+static int pattern_match_multiple_instances(
bd1529
+                        const PresetRule rule,
bd1529
+                        const char *unit_name,
bd1529
+                        char ***ret) {
bd1529
+
bd1529
+        _cleanup_free_ char *templated_name = NULL;
bd1529
+        int r;
bd1529
+
bd1529
+        /* If no ret is needed or the rule itself does not have instances
bd1529
+         * initalized, we return not matching */
bd1529
+        if (!ret || !rule.instances)
bd1529
+                return 0;
bd1529
+
bd1529
+        r = unit_name_template(unit_name, &templated_name);
bd1529
+        if (r < 0)
bd1529
+                return r;
bd1529
+        if (!streq(rule.pattern, templated_name))
bd1529
+                return 0;
bd1529
+
bd1529
+        /* Compose a list of specified instances when unit name is a template  */
bd1529
+        if (unit_name_is_valid(unit_name, UNIT_NAME_TEMPLATE)) {
bd1529
+                _cleanup_free_ char *prefix = NULL;
bd1529
+                _cleanup_strv_free_ char **out_strv = NULL;
bd1529
+                char **iter;
bd1529
+
bd1529
+                r = unit_name_to_prefix(unit_name, &prefix);
bd1529
+                if (r < 0)
bd1529
+                        return r;
bd1529
+
bd1529
+                STRV_FOREACH(iter, rule.instances) {
bd1529
+                        _cleanup_free_ char *name = NULL;
bd1529
+                        r = unit_name_build(prefix, *iter, ".service", &name);
bd1529
+                        if (r < 0)
bd1529
+                                return r;
bd1529
+                        r = strv_extend(&out_strv, name);
bd1529
+                        if (r < 0)
bd1529
+                                return r;
bd1529
+                }
bd1529
+
bd1529
+                *ret = TAKE_PTR(out_strv);
bd1529
+                return 1;
bd1529
+        } else {
bd1529
+                /* We now know the input unit name is an instance name */
bd1529
+                _cleanup_free_ char *instance_name = NULL;
bd1529
+
bd1529
+                r = unit_name_to_instance(unit_name, &instance_name);
bd1529
+                if (r < 0)
bd1529
+                        return r;
bd1529
+
bd1529
+                if (strv_find(rule.instances, instance_name))
bd1529
+                        return 1;
bd1529
+        }
bd1529
+        return 0;
bd1529
+}
bd1529
+
bd1529
+static int query_presets(const char *name, const Presets presets, char ***instance_name_list) {
bd1529
         PresetAction action = PRESET_UNKNOWN;
bd1529
         size_t i;
bd1529
-
bd1529
+        char **s;
bd1529
         if (!unit_name_is_valid(name, UNIT_NAME_ANY))
bd1529
                 return -EINVAL;
bd1529
 
bd1529
         for (i = 0; i < presets.n_rules; i++)
bd1529
-                if (fnmatch(presets.rules[i].pattern, name, FNM_NOESCAPE) == 0) {
bd1529
+                if (pattern_match_multiple_instances(presets.rules[i], name, instance_name_list) > 0 ||
bd1529
+                    fnmatch(presets.rules[i].pattern, name, FNM_NOESCAPE) == 0) {
bd1529
                         action = presets.rules[i].action;
bd1529
                         break;
bd1529
                 }
bd1529
@@ -2886,7 +2983,11 @@ static int query_presets(const char *name, const Presets presets) {
bd1529
                 log_debug("Preset files don't specify rule for %s. Enabling.", name);
bd1529
                 return 1;
bd1529
         case PRESET_ENABLE:
bd1529
-                log_debug("Preset files say enable %s.", name);
bd1529
+                if (instance_name_list && *instance_name_list)
bd1529
+                        STRV_FOREACH(s, *instance_name_list)
bd1529
+                                log_debug("Preset files say enable %s.", *s);
bd1529
+                else
bd1529
+                        log_debug("Preset files say enable %s.", name);
bd1529
                 return 1;
bd1529
         case PRESET_DISABLE:
bd1529
                 log_debug("Preset files say disable %s.", name);
bd1529
@@ -2904,7 +3005,7 @@ int unit_file_query_preset(UnitFileScope scope, const char *root_dir, const char
bd1529
         if (r < 0)
bd1529
                 return r;
bd1529
 
bd1529
-        return query_presets(name, presets);
bd1529
+        return query_presets(name, presets, NULL);
bd1529
 }
bd1529
 
bd1529
 static int execute_preset(
bd1529
@@ -2964,6 +3065,7 @@ static int preset_prepare_one(
bd1529
                 size_t *n_changes) {
bd1529
 
bd1529
         _cleanup_(install_context_done) InstallContext tmp = {};
bd1529
+        _cleanup_strv_free_ char **instance_name_list = NULL;
bd1529
         UnitFileInstallInfo *i;
bd1529
         int r;
bd1529
 
bd1529
@@ -2979,19 +3081,34 @@ static int preset_prepare_one(
bd1529
                 return 0;
bd1529
         }
bd1529
 
bd1529
-        r = query_presets(name, presets);
bd1529
+        r = query_presets(name, presets, &instance_name_list);
bd1529
         if (r < 0)
bd1529
                 return r;
bd1529
 
bd1529
         if (r > 0) {
bd1529
-                r = install_info_discover(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
bd1529
-                                          &i, changes, n_changes);
bd1529
-                if (r < 0)
bd1529
-                        return r;
bd1529
+                if (instance_name_list) {
bd1529
+                        char **s;
bd1529
+                        STRV_FOREACH(s, instance_name_list) {
bd1529
+                                r = install_info_discover(scope, plus, paths, *s, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
bd1529
+                                                          &i, changes, n_changes);
bd1529
+                                if (r < 0)
bd1529
+                                        return r;
bd1529
+
bd1529
+                                r = install_info_may_process(i, paths, changes, n_changes);
bd1529
+                                if (r < 0)
bd1529
+                                        return r;
bd1529
+                        }
bd1529
+                } else {
bd1529
+                        r = install_info_discover(scope, plus, paths, name, SEARCH_LOAD|SEARCH_FOLLOW_CONFIG_SYMLINKS,
bd1529
+                                                  &i, changes, n_changes);
bd1529
+                        if (r < 0)
bd1529
+                                return r;
bd1529
+
bd1529
+                        r = install_info_may_process(i, paths, changes, n_changes);
bd1529
+                        if (r < 0)
bd1529
+                                return r;
bd1529
+                }
bd1529
 
bd1529
-                r = install_info_may_process(i, paths, changes, n_changes);
bd1529
-                if (r < 0)
bd1529
-                        return r;
bd1529
         } else
bd1529
                 r = install_info_discover(scope, minus, paths, name, SEARCH_FOLLOW_CONFIG_SYMLINKS,
bd1529
                                           &i, changes, n_changes);
bd1529
diff --git a/src/test/test-install-root.c b/src/test/test-install-root.c
bd1529
index 15dd3c6966..dbbcfe4297 100644
bd1529
--- a/src/test/test-install-root.c
bd1529
+++ b/src/test/test-install-root.c
bd1529
@@ -983,6 +983,62 @@ static void test_with_dropin_template(const char *root) {
bd1529
         assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "with-dropin-3@instance-2.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
bd1529
 }
bd1529
 
bd1529
+static void test_preset_multiple_instances(const char *root) {
bd1529
+        UnitFileChange *changes = NULL;
bd1529
+        size_t n_changes = 0;
bd1529
+        const char *p;
bd1529
+        UnitFileState state;
bd1529
+
bd1529
+        /* Set up template service files and preset file */
bd1529
+        p = strjoina(root, "/usr/lib/systemd/system/foo@.service");
bd1529
+        assert_se(write_string_file(p,
bd1529
+                                    "[Install]\n"
bd1529
+                                    "DefaultInstance=def\n"
bd1529
+                                    "WantedBy=multi-user.target\n", WRITE_STRING_FILE_CREATE) >= 0);
bd1529
+
bd1529
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
bd1529
+
bd1529
+        p = strjoina(root, "/usr/lib/systemd/system-preset/test.preset");
bd1529
+        assert_se(write_string_file(p,
bd1529
+                                    "enable foo@.service bar0 bar1 bartest\n"
bd1529
+                                    "enable emptylist@.service\n" /* This line ensures the old functionality for templated unit still works */
bd1529
+                                    "disable *\n" , WRITE_STRING_FILE_CREATE) >= 0);
bd1529
+
bd1529
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar0.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
bd1529
+
bd1529
+        /* Preset a single instantiated unit specified in the list */
bd1529
+        assert_se(unit_file_preset(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("foo@bar0.service"), UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
bd1529
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar0.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
bd1529
+        assert_se(n_changes == 1);
bd1529
+        assert_se(changes[0].type == UNIT_FILE_SYMLINK);
bd1529
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/foo@bar0.service");
bd1529
+        assert_se(streq(changes[0].path, p));
bd1529
+        unit_file_changes_free(changes, n_changes);
bd1529
+        changes = NULL; n_changes = 0;
bd1529
+
bd1529
+        assert_se(unit_file_disable(UNIT_FILE_SYSTEM, 0, root, STRV_MAKE("foo@bar0.service"), &changes, &n_changes) >= 0);
bd1529
+        assert_se(n_changes == 1);
bd1529
+        assert_se(changes[0].type == UNIT_FILE_UNLINK);
bd1529
+        p = strjoina(root, SYSTEM_CONFIG_UNIT_PATH"/multi-user.target.wants/foo@bar0.service");
bd1529
+        assert_se(streq(changes[0].path, p));
bd1529
+        unit_file_changes_free(changes, n_changes);
bd1529
+        changes = NULL; n_changes = 0;
bd1529
+
bd1529
+        /* Check for preset-all case, only instances on the list should be enabled, not including the default instance */
bd1529
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
bd1529
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar1.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
bd1529
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bartest.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
bd1529
+
bd1529
+        assert_se(unit_file_preset_all(UNIT_FILE_SYSTEM, 0, root, UNIT_FILE_PRESET_FULL, &changes, &n_changes) >= 0);
bd1529
+        assert_se(n_changes > 0);
bd1529
+
bd1529
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@def.service", &state) >= 0 && state == UNIT_FILE_DISABLED);
bd1529
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar0.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
bd1529
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bar1.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
bd1529
+        assert_se(unit_file_get_state(UNIT_FILE_SYSTEM, root, "foo@bartest.service", &state) >= 0 && state == UNIT_FILE_ENABLED);
bd1529
+
bd1529
+}
bd1529
+
bd1529
 int main(int argc, char *argv[]) {
bd1529
         char root[] = "/tmp/rootXXXXXX";
bd1529
         const char *p;
bd1529
@@ -1012,6 +1068,7 @@ int main(int argc, char *argv[]) {
bd1529
         test_indirect(root);
bd1529
         test_preset_and_list(root);
bd1529
         test_preset_order(root);
bd1529
+        test_preset_multiple_instances(root);
bd1529
         test_revert(root);
bd1529
         test_static_instance(root);
bd1529
         test_with_dropin(root);