sysupdate: optionally load a per-component *.component file

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
This commit is contained in:
Lennart Poettering
2026-06-18 18:02:37 +02:00
parent 001c5dfdf5
commit 291e6118f5
3 changed files with 87 additions and 11 deletions

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

View 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 dont
* exist, images which the user isnt 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;

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