]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysupdate: add support for --component-all + --feature-all + --feature-suggested... 42970/head
authorLennart Poettering <lennart@amutable.com>
Tue, 30 Jun 2026 19:46:46 +0000 (21:46 +0200)
committerLennart Poettering <lennart@amutable.com>
Mon, 13 Jul 2026 15:36:11 +0000 (17:36 +0200)
This adds similar concepts as we already have for enable-component:
let's add a way to enable all features or the suggeste dones, and
possibly on all components.

Or in other words, with this:

  systemd-sysupdate enable-component -S
  systemd-sysupdate enable-feature -A -s

We'll automatically enable all suggested components, and all features of
them.

src/sysupdate/sysupdate.c

index f334756b9f8f549541e28a0390b6782006995117..01b4a03f4d3531550eca87f9a7d5384b6c6b52be 100644 (file)
@@ -2025,9 +2025,114 @@ static int make_dropin_dir(Context *c, char **ret) {
         return 0;
 }
 
-VERB(verb_enable_feature, "enable-feature", "FEATURE…", 2, VERB_ANY, 0,
+static const char *context_component_display(const Context *c) {
+        assert(c);
+
+        return c->component ?: "<default>";
+}
+
+static int context_enable_feature(
+                Context *c,
+                const char *argv0,
+                bool enable,
+                char **features) {
+
+        int r;
+
+        assert(c);
+        assert(argv0);
+
+        _cleanup_free_ char **l = NULL;
+        switch (c->feature_select) {
+
+        case SELECT_EXPLICIT:
+                break;
+
+        case SELECT_ALL: {
+                assert(!features);
+
+                Feature *f;
+                HASHMAP_FOREACH(f, c->features)
+                        if (strv_push(&l, f->id) < 0)
+                                return log_oom();
+
+                features = l;
+                break;
+        }
+
+        case SELECT_SUGGESTED: {
+                assert(!features);
+
+                Feature *f;
+                HASHMAP_FOREACH(f, c->features) {
+                        r = feature_is_suggested(f);
+                        if (r < 0)
+                                return log_error_errno(r, "Failed to determine if feature '%s' of component '%s' shall be enabled: %m", f->id, context_component_display(c));
+                        if (!!r != !!enable) {
+                                log_debug("Skipping feature '%s' of component '%s'.", f->id, context_component_display(c));
+                                continue;
+                        }
+
+                        log_info("%s feature '%s' of component '%s'.", enable ? "Enabling" : "Disabling", f->id, context_component_display(c));
+
+                        if (strv_push(&l, f->id) < 0)
+                                return log_oom();
+                }
+
+                features = l;
+                break;
+        }
+
+        default:
+                assert_not_reached();
+        }
+
+        if (strv_isempty(features)) {
+                log_debug("No features selected.");
+                return 0;
+        }
+
+        _cleanup_free_ char *dropin_dir = NULL;
+        r = make_dropin_dir(c, &dropin_dir);
+        if (r < 0)
+                return r;
+
+        int ret = 0;
+        STRV_FOREACH(name, features) {
+                if (!hashmap_contains(c->features, *name)) {
+                        RET_GATHER(ret, log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Optional feature not found in component '%s': %s", context_component_display(c), *name));
+                        continue;
+                }
+
+                _cleanup_free_ char *fname = strjoin(*name, ".feature");
+                if (!fname)
+                        return log_oom();
+
+                /* We assume that no sysadmin will name their config 50-systemd-sysupdate-enabled.conf */
+                r = write_drop_in_format(
+                                dropin_dir,
+                                fname,
+                                50,
+                                "systemd-sysupdate-enabled",
+                                "# Generated via 'systemd-sysupdate %s'\n\n"
+                                "[Feature]\n"
+                                "Enabled=%s\n",
+                                argv0,
+                                yes_no(enable));
+                if (r < 0) {
+                        RET_GATHER(ret, log_error_errno(r, "Failed to write drop-in for feature '%s': %m", *name));
+                        continue;
+                }
+
+                log_info("Feature '%s' %s.", *name, enabled_disabled(enable));
+        }
+
+        return ret;
+}
+
+VERB(verb_enable_feature, "enable-feature", "FEATURE…", 1, VERB_ANY, 0,
      "Enable optional feature");
-VERB(verb_enable_feature, "disable-feature", "FEATURE…", 2, VERB_ANY, 0,
+VERB(verb_enable_feature, "disable-feature", "FEATURE…", 1, VERB_ANY, 0,
      "Disable optional feature");
 static int verb_enable_feature(int argc, char *argv[], uintptr_t _data, void *userdata) {
         bool enable = streq(argv[0], "enable-feature");
@@ -2038,10 +2143,24 @@ static int verb_enable_feature(int argc, char *argv[], uintptr_t _data, void *us
         if (r < 0)
                 return r;
 
-        if (context.feature_select != SELECT_EXPLICIT)
-                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--feature-all/--feature-suggested is not supported for '%s'.", argv[0]);
-        if (context.component_select != SELECT_EXPLICIT)
-                return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
+        if (!IN_SET(context.component_select, SELECT_EXPLICIT, SELECT_ALL))
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--component-suggested is not supported for '%s'.", argv[0]);
+        if (context.definitions)
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL),
+                                       "The --definitions= switch may not be combined with '%s'.", argv[0]);
+
+        char **features;
+        if (argc > 1 && context.feature_select == SELECT_EXPLICIT) {
+                features = strv_skip(argv, 1);
+
+                STRV_FOREACH(name, features)
+                        if (!feature_name_valid(*name))
+                                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Feature name invalid: %s", *name);
+
+        } else if (argc <= 1 && context.feature_select != SELECT_EXPLICIT)
+                features = NULL;
+        else
+                return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Either specify features as positional parameter or via --feature-all/--feature-suggested, not both.");
 
         r = context_load_offline(
                         &context,
@@ -2050,41 +2169,54 @@ static int verb_enable_feature(int argc, char *argv[], uintptr_t _data, void *us
         if (r < 0)
                 return r;
 
-        _cleanup_free_ char *dropin_dir = NULL;
-        r = make_dropin_dir(&context, &dropin_dir);
-        if (r < 0)
-                return r;
+        switch (context.component_select) {
 
-        STRV_FOREACH(name, strv_skip(argv, 1)) {
+        case SELECT_EXPLICIT:
+                return context_enable_feature(&context, argv[0], enable, features);
 
-                if (!feature_name_valid(*name))
-                        return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Feature name invalid: %s", *name);
+        case SELECT_ALL: {
+                _cleanup_strv_free_ char **component_names = NULL;
+                bool has_default_component;
+                r = context_list_components(&context, &component_names, &has_default_component);
+                if (r < 0)
+                        return r;
 
-                if (!hashmap_contains(context.features, *name))
-                        return log_error_errno(SYNTHETIC_ERRNO(ENOENT),
-                                               "Optional feature not found: %s", *name);
+                if (strv_isempty(component_names) && !has_default_component) {
+                        log_debug("No components selected.");
+                        return 0;
+                }
 
-                _cleanup_free_ char *fname = strjoin(*name, ".feature");
-                if (!fname)
-                        return log_oom();
+                int ret = 0;
+                if (has_default_component)
+                        RET_GATHER(ret, context_enable_feature(&context, argv[0], enable, features));
 
-                /* We assume that no sysadmin will name their config 50-systemd-sysupdate-enabled.conf */
-                r = write_drop_in_format(
-                                dropin_dir,
-                                fname,
-                                50, "systemd-sysupdate-enabled",
-                                "# Generated via 'systemd-sysupdate %s'\n\n"
-                                "[Feature]\n"
-                                "Enabled=%s\n",
-                                argv[0],
-                                yes_no(enable));
-                if (r < 0)
-                        return log_error_errno(r, "Failed to write drop-in for feature '%s': %m", *name);
+                STRV_FOREACH(name, component_names) {
+                        _cleanup_(context_done) Context cc = CONTEXT_NULL;
 
-                log_info("Feature '%s' %s.", *name, enable ? "enabled" : "disabled");
+                        r = context_from_base_with_component(&context, *name, &cc);
+                        if (r < 0) {
+                                RET_GATHER(ret, r);
+                                continue;
+                        }
+
+                        r = context_load_offline(
+                                        &cc,
+                                        /* process_image_flags= */ 0,
+                                        /* read_definitions_flags= */ 0);
+                        if (r < 0) {
+                                RET_GATHER(ret, r);
+                                continue;
+                        }
+
+                        RET_GATHER(ret, context_enable_feature(&cc, argv[0], enable, features));
+                }
+
+                return ret;
         }
 
-        return 0;
+        default:
+                assert_not_reached();
+        }
 }
 
 VERB_NOARG(verb_check_new, "check-new",