]> git.ipfire.org Git - thirdparty/systemd.git/commitdiff
sysupdate: optionally load a per-component *.component file
authorLennart Poettering <lennart@amutable.com>
Thu, 18 Jun 2026 16:02:37 +0000 (18:02 +0200)
committerLennart Poettering <lennart@amutable.com>
Thu, 9 Jul 2026 07:56:48 +0000 (09:56 +0200)
This file can be used for metadata about a component. It may also be
used to "disable" a component.

This brings components as a concept to a similar conceptual level as
features: both can be enabled/disabled, and carry metadata

src/sysupdate/sysupdate-util.c
src/sysupdate/sysupdate.c
src/sysupdate/sysupdate.h

index b3ed72577e911a5a5a1f71bf8c7adfcd3f5b2d74..8b0a4924e4cd6168f2d6f522c5c6d3c575322028 100644 (file)
@@ -37,6 +37,9 @@ bool component_name_valid(const char *c) {
         if (!string_is_safe(c, STRING_FILENAME_PART))
                 return false;
 
+        if (endswith(c, ".component")) /* don't allow ambiguity around sysupdate.<foobar>.component.d/ */
+                return false;
+
         /* Stack allocation is safe, since STRING_FILENAME_PART includes a length check */
         const char *j = strjoina("sysupdate.", c, ".d");
 
index 2a277026b007c2aecf8cc0db760e9fc50ea75504..0704aabd9e361d31451cb6caecb7d00f5cca0098 100644 (file)
@@ -10,6 +10,7 @@
 #include "build.h"
 #include "bus-polkit.h"
 #include "conf-files.h"
+#include "conf-parser.h"
 #include "constants.h"
 #include "discover-image.h"
 #include "dissect-image.h"
@@ -38,6 +39,7 @@
 #include "strv.h"
 #include "sysupdate.h"
 #include "sysupdate-cleanup.h"
+#include "sysupdate-config.h"
 #include "sysupdate-feature.h"
 #include "sysupdate-instance.h"
 #include "sysupdate-target.h"
@@ -75,6 +77,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_transfer_source, freep);
 
 #define CONTEXT_NULL                                              \
         (Context) {                                               \
+                .component_enabled = true,                        \
                 .sync = true,                                     \
                 .instances_max = UINT64_MAX,                      \
                 .verify = -1,                                     \
@@ -114,6 +117,8 @@ void context_done(Context *c) {
         c->root = mfree(c->root);
         c->image = mfree(c->image);
         c->component = mfree(c->component);
+        c->component_description = mfree(c->component_description);
+        c->component_documentation = strv_free(c->component_documentation);
         c->image_policy = image_policy_free(c->image_policy);
         c->transfer_source = mfree(c->transfer_source);
 
@@ -280,9 +285,46 @@ static int read_transfers(
         return 0;
 }
 
+static int read_component(Context *c) {
+        int r;
+
+        assert(c);
+
+        /* Read a component description file, but only if we actually operate on a component */
+        if (c->definitions || !c->component)
+                return 0;
+
+        _cleanup_free_ char *j = strjoin("sysupdate.", c->component, ".component");
+        if (!j)
+                return log_oom();
+
+        ConfigTableItem table[] = {
+                { "Component", "Description",   config_parse_string,              0, &c->component_description   },
+                { "Component", "Documentation", config_parse_url_specifiers_many, 0, &c->component_documentation },
+                { "Component", "Enabled",       config_parse_bool,                0, &c->component_enabled       },
+                {}
+        };
+
+        r = config_parse_standard_file_with_dropins_full(
+                        c->root,
+                        /* root_fd= */ -EBADF,
+                        j,
+                        "Component\0",
+                        config_item_table_lookup, table,
+                        CONFIG_PARSE_WARN,
+                        /* userdata= */ (void *) c->root,
+                        /* ret_stats_by_path= */ NULL,
+                        /* ret_dropin_files= */ NULL);
+        if (r < 0)
+                return r;
+
+        return 0;
+}
+
 typedef enum ReadDefinitionsFlags {
-        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0,
-        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS     = 1 << 1,
+        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0, /* fail unless there's at least one enabled transfer */
+        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS     = 1 << 1, /* fail unless there's at least one transfer */
+        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT = 1 << 2, /* fail if component is disabled */
 } ReadDefinitionsFlags;
 
 static int context_read_definitions(Context *c, const char* node, ReadDefinitionsFlags flags) {
@@ -315,6 +357,10 @@ static int context_read_definitions(Context *c, const char* node, ReadDefinition
         if (!dirs)
                 return log_oom();
 
+        r = read_component(c);
+        if (r < 0)
+                return r;
+
         r = read_features(c, (const char**) dirs);
         if (r < 0)
                 return r;
@@ -344,6 +390,9 @@ static int context_read_definitions(Context *c, const char* node, ReadDefinition
                                        "No transfer definitions found.");
         }
 
+        if (FLAGS_SET(flags, READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT) && !c->component_enabled)
+                return log_error_errno(SYNTHETIC_ERRNO(EHOSTDOWN), "Component is disabled.");
+
         return 0;
 }
 
@@ -1094,7 +1143,9 @@ static int context_load_offline(
 
 static int context_load_online(
                 Context *context,
-                ProcessImageFlags process_image_flags) {
+                ProcessImageFlags process_image_flags,
+                ReadDefinitionsFlags read_definitions_flags) {
+
         int r;
 
         assert(context);
@@ -1105,7 +1156,7 @@ static int context_load_online(
         r = context_load_offline(
                         context,
                         process_image_flags,
-                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
+                        read_definitions_flags);
         if (r < 0)
                 return r;
 
@@ -1156,7 +1207,10 @@ static int context_load_paths_from_image(Context *context, Image *image) {
  * syntactically validated by dispatch_target_identifier(), but might still point to components which don’t
  * exist, images which the user isn’t privileged to access, etc. This function validates the TargetIdentifier
  * against an enumerated list of known targets, which are safe to update without additional permissions. */
-static int context_load_online_from_target(Context *context, ProcessImageFlags process_image_flags) {
+static int context_load_online_from_target(
+                Context *context,
+                ProcessImageFlags process_image_flags,
+                ReadDefinitionsFlags read_definitions_flags) {
         int r;
 
         assert(context);
@@ -1247,7 +1301,7 @@ static int context_load_online_from_target(Context *context, ProcessImageFlags p
                 assert_not_reached();
         }
 
-        return context_load_online(context, process_image_flags);
+        return context_load_online(context, process_image_flags, read_definitions_flags);
 }
 
 static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) {
@@ -1650,7 +1704,11 @@ static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) {
         if (context.component_all)
                 return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]);
 
-        r = context_load_online(&context, PROCESS_IMAGE_READ_ONLY);
+        r = context_load_online(
+                        &context,
+                        PROCESS_IMAGE_READ_ONLY,
+                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
+                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
         if (r < 0)
                 return r;
 
@@ -1860,7 +1918,9 @@ static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdat
 
         r = context_load_online(
                         &context,
-                        PROCESS_IMAGE_READ_ONLY);
+                        PROCESS_IMAGE_READ_ONLY,
+                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
+                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
         if (r < 0)
                 return r;
 
@@ -1919,7 +1979,11 @@ static int vl_method_check_new(sd_varlink *link, sd_json_variant *parameters, sd
         /* CheckNew is always online */
         context.offline = false;
 
-        r = context_load_online_from_target(&context, PROCESS_IMAGE_READ_ONLY);
+        r = context_load_online_from_target(
+                        &context,
+                        PROCESS_IMAGE_READ_ONLY,
+                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
+                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
         if (r == -ENOENT)
                 return sd_varlink_error(link, "io.systemd.SysUpdate.NoSuchTarget", NULL);
         if (r < 0)
@@ -1976,7 +2040,10 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag
 
         r = context_load_online(
                         &context,
-                        /* process_image_flags= */ 0);
+                        /* process_image_flags= */ 0,
+                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
+                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS|
+                        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT);
         if (r < 0) {
                 if (r != -ENOENT)
                         return r;
@@ -2108,7 +2175,9 @@ static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void
         r = context_load_offline(
                         &context,
                         /* process_image_flags= */ 0,
-                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS);
+                        READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|
+                        READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS|
+                        READ_DEFINITIONS_REQUIRES_ENABLED_COMPONENT);
         if (r < 0)
                 return r;
 
index 3dcaae527d1969dfb92bfbda617f7d93c11f8915..6088bde669bff863c652f2d93957dd79dac1d30d 100644 (file)
@@ -24,6 +24,10 @@ typedef struct Context {
         LoopDevice *loop_device;
         char *mounted_dir;
 
+        char *component_description;
+        char **component_documentation;
+        bool component_enabled;
+
         Transfer **transfers;
         size_t n_transfers;