diff --git a/NEWS b/NEWS index e482c3f898..d34215377e 100644 --- a/NEWS +++ b/NEWS @@ -8,6 +8,14 @@ CHANGES WITH 262: the symlink target. Previously, these were only expanded in the source. + * As part of changes to systemd-sysupdate, the existing + "systemd-sysupdate.service" and "systemd-sysupdate.timer" units – + which periodically ran "systemd-sysupdate" to update the host system – + have been renamed to "systemd-sysupdate-update.service" and + "systemd-sysupdate-update.timer" respectively. Compatibility symlinks + have been provided. This clears the way for a new + "systemd-sysupdate@.service" unit for varlink activation of sysupdate. + CHANGES WITH 261: Announcements of Future Feature Removals and Incompatible Changes: diff --git a/man/rules/meson.build b/man/rules/meson.build index 974d9eee4b..13025b8993 100644 --- a/man/rules/meson.build +++ b/man/rules/meson.build @@ -1267,6 +1267,8 @@ manpages = [ '8', ['systemd-sysupdate-reboot.service', 'systemd-sysupdate-reboot.timer', + 'systemd-sysupdate-update.service', + 'systemd-sysupdate-update.timer', 'systemd-sysupdate.service', 'systemd-sysupdate.timer'], 'ENABLE_SYSUPDATE'], diff --git a/man/systemd-sysupdate.xml b/man/systemd-sysupdate.xml index 7f4b089031..2855c650c8 100644 --- a/man/systemd-sysupdate.xml +++ b/man/systemd-sysupdate.xml @@ -18,10 +18,15 @@ systemd-sysupdate - systemd-sysupdate.service - systemd-sysupdate.timer + systemd-sysupdate-update.service + systemd-sysupdate-update.timer systemd-sysupdate-reboot.service systemd-sysupdate-reboot.timer + + + systemd-sysupdate.service + systemd-sysupdate.timer + Automatically Update OS or Other Resources @@ -31,7 +36,7 @@ OPTIONS - systemd-sysupdate.service + systemd-sysupdate-update.service @@ -73,9 +78,9 @@ embedded in the disk images. For the latter, see below. The latter is particularly interesting to update container images or portable service images. - The systemd-sysupdate.service system service will automatically update the + The systemd-sysupdate-update.service system service will automatically update the host OS based on the installed transfer files. It is triggered in regular intervals via - systemd-sysupdate.timer. The systemd-sysupdate-reboot.service + systemd-sysupdate-update.timer. The systemd-sysupdate-reboot.service will automatically reboot the system after a new version is installed. It is triggered via systemd-sysupdate-reboot.timer. The two services are separate from each other as it is typically advisable to download updates regularly while the system is up, but delay reboots until the diff --git a/src/libsystemd/sd-json/sd-json.c b/src/libsystemd/sd-json/sd-json.c index dad7a3174d..531429b51f 100644 --- a/src/libsystemd/sd-json/sd-json.c +++ b/src/libsystemd/sd-json/sd-json.c @@ -5359,7 +5359,7 @@ _public_ int sd_json_dispatch_full( for (const sd_json_dispatch_field *p = table; p && p->name; p++) { sd_json_dispatch_flags_t merged_flags = p->flags | flags; - if ((merged_flags & SD_JSON_MANDATORY) && !found[p-table]) { + if ((p->flags & SD_JSON_MANDATORY) && !found[p-table]) { json_log(v, merged_flags, 0, "Missing object field '%s'.", p->name); if ((merged_flags & SD_JSON_PERMISSIVE)) diff --git a/src/libsystemd/sd-varlink/test-varlink-idl.c b/src/libsystemd/sd-varlink/test-varlink-idl.c index 0b3304a43c..a1ed9a0f42 100644 --- a/src/libsystemd/sd-varlink/test-varlink-idl.c +++ b/src/libsystemd/sd-varlink/test-varlink-idl.c @@ -51,6 +51,7 @@ #include "varlink-io.systemd.Resolve.Monitor.h" #include "varlink-io.systemd.Shutdown.h" #include "varlink-io.systemd.StorageProvider.h" +#include "varlink-io.systemd.SysUpdate.h" #include "varlink-io.systemd.SysUpdate.Notify.h" #include "varlink-io.systemd.Udev.h" #include "varlink-io.systemd.Unit.h" @@ -228,6 +229,7 @@ TEST(parse_format) { &vl_interface_io_systemd_Resolve_Monitor, &vl_interface_io_systemd_Shutdown, &vl_interface_io_systemd_StorageProvider, + &vl_interface_io_systemd_SysUpdate, &vl_interface_io_systemd_SysUpdate_Notify, &vl_interface_io_systemd_Udev, &vl_interface_io_systemd_Unit, diff --git a/src/libsystemd/sd-varlink/test-varlink.c b/src/libsystemd/sd-varlink/test-varlink.c index 31b7cb46f9..a2a32b7f78 100644 --- a/src/libsystemd/sd-varlink/test-varlink.c +++ b/src/libsystemd/sd-varlink/test-varlink.c @@ -452,6 +452,78 @@ TEST(invalid_parameter) { ASSERT_OK(sd_event_loop(e)); } +typedef struct MandatoryTypeWithOptionalField { + const char *mandatory; + const char *optional; +} MandatoryTypeWithOptionalField; + +static int dispatch_mandatory_type_with_optional_field(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + MandatoryTypeWithOptionalField *m = ASSERT_PTR(userdata); + static const sd_json_dispatch_field dispatch[] = { + { "mandatory", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(*m, mandatory), SD_JSON_MANDATORY }, + { "optional", SD_JSON_VARIANT_STRING, sd_json_dispatch_const_string, voffsetof(*m, optional), 0 }, + {} + }; + + return sd_json_dispatch(variant, dispatch, flags, m); +} + +static int method_mandatory_type_with_optional_field(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + MandatoryTypeWithOptionalField m = { NULL, NULL }; + int r; + + sd_json_dispatch_field table[] = { + { "mandatoryField", SD_JSON_VARIANT_OBJECT, dispatch_mandatory_type_with_optional_field, 0, SD_JSON_MANDATORY }, + {} + }; + + r = sd_varlink_dispatch(link, parameters, table, &m); + if (r != 0) + return r; + + _cleanup_free_ char *sum = strjoin(m.mandatory, "+", m.optional); + if (!sum) + return log_oom(); + + return sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("result", sum)); +} + +static int reply_valid_hi(sd_varlink *link, sd_json_variant *parameters, const char *error_id, sd_varlink_reply_flags_t flags, void *userdata) { + ASSERT_NULL(error_id); + ASSERT_STREQ(sd_json_variant_string(sd_json_variant_by_key(parameters, "result")), "hi+"); + ASSERT_OK(sd_event_exit(sd_varlink_get_event(link), EXIT_SUCCESS)); + return 0; +} + +TEST(mandatory_type_with_optional_field) { + _cleanup_(sd_event_unrefp) sd_event *e = NULL; + ASSERT_OK(sd_event_default(&e)); + + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *s = NULL; + ASSERT_OK(sd_varlink_server_new(&s, 0)); + + ASSERT_OK(sd_varlink_server_attach_event(s, e, 0)); + + ASSERT_OK(sd_varlink_server_bind_method(s, "foo.mytest.MandatoryTypeWithOptionalField", method_mandatory_type_with_optional_field)); + + int connfd[2]; + ASSERT_OK_ERRNO(socketpair(AF_UNIX, SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC, 0, connfd)); + ASSERT_OK(sd_varlink_server_add_connection(s, connfd[0], /* ret= */ NULL)); + + _cleanup_(sd_varlink_unrefp) sd_varlink *c = NULL; + ASSERT_OK(sd_varlink_connect_fd(&c, connfd[1])); + + ASSERT_OK(sd_varlink_attach_event(c, e, 0)); + + ASSERT_OK(sd_varlink_bind_reply(c, reply_valid_hi)); + + ASSERT_OK(sd_varlink_invokebo(c, "foo.mytest.MandatoryTypeWithOptionalField", + SD_JSON_BUILD_PAIR_OBJECT("mandatoryField", + SD_JSON_BUILD_PAIR_STRING("mandatory", "hi")))); + + ASSERT_OK(sd_event_loop(e)); +} + static int method_with_error_sentinel(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { /* Set an error sentinel and return without sending a reply. The sentinel error should be sent automatically. */ ASSERT_OK(sd_varlink_set_sentinel(link, "io.test.SentinelError")); diff --git a/src/shared/image-policy.c b/src/shared/image-policy.c index 59d1161d04..622b5c2aa1 100644 --- a/src/shared/image-policy.c +++ b/src/shared/image-policy.c @@ -948,6 +948,12 @@ ImagePolicy* image_policy_free(ImagePolicy *p) { return mfree(p); } +ImagePolicy* image_policy_copy(const ImagePolicy *p) { + assert(p); + + return memdup(p, offsetof(ImagePolicy, policies) + sizeof(PartitionPolicy) * p->n_policies); +} + int image_policy_ignore_designators(const ImagePolicy *p, const PartitionDesignator table[], size_t n_table, ImagePolicy **ret) { assert(p); assert(table || n_table == 0); diff --git a/src/shared/image-policy.h b/src/shared/image-policy.h index fe2bf0d200..b0c229e71e 100644 --- a/src/shared/image-policy.h +++ b/src/shared/image-policy.h @@ -119,6 +119,8 @@ ImagePolicy* image_policy_free(ImagePolicy *p); DEFINE_TRIVIAL_CLEANUP_FUNC(ImagePolicy*, image_policy_free); +ImagePolicy* image_policy_copy(const ImagePolicy *p); + CONFIG_PARSER_PROTOTYPE(config_parse_image_policy); int parse_image_policy_argument(const char *s, ImagePolicy **policy); diff --git a/src/shared/meson.build b/src/shared/meson.build index 6fa4216305..e2bef83bd2 100644 --- a/src/shared/meson.build +++ b/src/shared/meson.build @@ -251,6 +251,7 @@ shared_sources = files( 'varlink-io.systemd.Resolve.Monitor.c', 'varlink-io.systemd.Shutdown.c', 'varlink-io.systemd.StorageProvider.c', + 'varlink-io.systemd.SysUpdate.c', 'varlink-io.systemd.SysUpdate.Notify.c', 'varlink-io.systemd.Udev.c', 'varlink-io.systemd.Unit.c', diff --git a/src/shared/varlink-io.systemd.SysUpdate.c b/src/shared/varlink-io.systemd.SysUpdate.c new file mode 100644 index 0000000000..e80142f9c5 --- /dev/null +++ b/src/shared/varlink-io.systemd.SysUpdate.c @@ -0,0 +1,58 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "bus-polkit.h" +#include "varlink-io.systemd.SysUpdate.h" + +static SD_VARLINK_DEFINE_ENUM_TYPE( + TargetClass, + SD_VARLINK_FIELD_COMMENT("Container or machine managed by systemd-machined.service(8)."), + SD_VARLINK_DEFINE_ENUM_VALUE(machine), + SD_VARLINK_FIELD_COMMENT("Portable service."), + SD_VARLINK_DEFINE_ENUM_VALUE(portable), + SD_VARLINK_FIELD_COMMENT("System extension managed by systemd-sysext.service(8)."), + SD_VARLINK_DEFINE_ENUM_VALUE(sysext), + SD_VARLINK_FIELD_COMMENT("Configuration extension managed by systemd-confext.service(8)."), + SD_VARLINK_DEFINE_ENUM_VALUE(confext), + SD_VARLINK_FIELD_COMMENT("Host system."), + SD_VARLINK_DEFINE_ENUM_VALUE(host), + SD_VARLINK_FIELD_COMMENT("Component managed by systemd-sysupdate.service(8)."), + SD_VARLINK_DEFINE_ENUM_VALUE(component)); + +static SD_VARLINK_DEFINE_STRUCT_TYPE( + TargetIdentifier, + SD_VARLINK_FIELD_COMMENT("Where the target was enumerated."), + SD_VARLINK_DEFINE_FIELD_BY_TYPE(class, TargetClass, 0), + SD_VARLINK_FIELD_COMMENT("Name of the target, unique within a class."), + SD_VARLINK_DEFINE_FIELD(name, SD_VARLINK_STRING, SD_VARLINK_NULLABLE)); + +static SD_VARLINK_DEFINE_METHOD( + CheckNew, + SD_VARLINK_FIELD_COMMENT("Target to check for updates for."), + SD_VARLINK_DEFINE_INPUT_BY_TYPE(target, TargetIdentifier, 0), + VARLINK_DEFINE_POLKIT_INPUT, + SD_VARLINK_FIELD_COMMENT("The version string for the new version which is available."), + SD_VARLINK_DEFINE_OUTPUT(available, SD_VARLINK_STRING, 0)); + +static SD_VARLINK_DEFINE_ERROR(NoUpdateNeeded); +static SD_VARLINK_DEFINE_ERROR(NoSuchTarget); + +SD_VARLINK_DEFINE_INTERFACE( + io_systemd_SysUpdate, + "io.systemd.SysUpdate", + SD_VARLINK_INTERFACE_COMMENT("APIs to manage system updates"), + + /* Methods */ + SD_VARLINK_SYMBOL_COMMENT("Check if there’s a new version available"), + &vl_method_CheckNew, + + /* Types */ + SD_VARLINK_SYMBOL_COMMENT("Class of a Target."), + &vl_type_TargetClass, + SD_VARLINK_SYMBOL_COMMENT("Identifier for a component of the system (i.e. the host itself, a sysext, a confext, etc.) that can be updated by systemd-sysupdate(8)."), + &vl_type_TargetIdentifier, + + /* Errors */ + SD_VARLINK_SYMBOL_COMMENT("Error indicating that no update is currently available to update to."), + &vl_error_NoUpdateNeeded, + SD_VARLINK_SYMBOL_COMMENT("Error indicating the specified target doesn’t exist"), + &vl_error_NoSuchTarget); diff --git a/src/shared/varlink-io.systemd.SysUpdate.h b/src/shared/varlink-io.systemd.SysUpdate.h new file mode 100644 index 0000000000..8399cdfbc3 --- /dev/null +++ b/src/shared/varlink-io.systemd.SysUpdate.h @@ -0,0 +1,6 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "sd-varlink-idl.h" + +extern const sd_varlink_interface vl_interface_io_systemd_SysUpdate; diff --git a/src/sysupdate/meson.build b/src/sysupdate/meson.build index ce1f4c14e7..b6f9436606 100644 --- a/src/sysupdate/meson.build +++ b/src/sysupdate/meson.build @@ -8,6 +8,7 @@ systemd_sysupdate_sources = files( 'sysupdate-partition.c', 'sysupdate-pattern.c', 'sysupdate-resource.c', + 'sysupdate-target.c', 'sysupdate-transfer.c', 'sysupdate-update-set.c', 'sysupdate.c', @@ -37,7 +38,7 @@ executables += [ 'name' : 'systemd-sysupdated', 'dbus' : true, 'conditions' : ['ENABLE_SYSUPDATED'], - 'sources' : files('sysupdated.c'), + 'sources' : files('sysupdated.c', 'sysupdate-target.c'), }, executable_template + { 'name' : 'updatectl', diff --git a/src/sysupdate/sysupdate-cleanup.c b/src/sysupdate/sysupdate-cleanup.c index fac2a0b1b3..2d0229423d 100644 --- a/src/sysupdate/sysupdate-cleanup.c +++ b/src/sysupdate/sysupdate-cleanup.c @@ -57,7 +57,7 @@ static int context_installdb_acquire_fd(Context *c, bool make) { assert(c); if (c->installdb_fd >= 0) - return 0; + return 1; _cleanup_free_ char *j = NULL; const char *p; @@ -77,14 +77,14 @@ static int context_installdb_acquire_fd(Context *c, bool make) { c->installdb_fd = chase_and_open( p, - arg_root, + c->root, flags, O_DIRECTORY|O_CLOEXEC|(make ? O_CREAT : 0), /* ret_path= */ NULL); if (c->installdb_fd == -ENOENT && !make) return 0; if (c->installdb_fd < 0) - return log_error_errno(c->installdb_fd, "Failed to open install database '%s%s': %m", empty_or_root(arg_root) ? "" : arg_root, p); + return log_error_errno(c->installdb_fd, "Failed to open install database '%s%s': %m", empty_or_root(c->root) ? "" : c->root, p); return 1; } @@ -128,8 +128,8 @@ int context_installdb_record( if (strv_isempty(patterns)) return 0; - /* The provided path comes with arg_root prefixed. Strip it here again */ - const char *p = arg_root ? ASSERT_PTR(path_startswith(path, arg_root)) : path; + /* The provided path comes with c->root prefixed. Strip it here again */ + const char *p = c->root ? ASSERT_PTR(path_startswith(path, c->root)) : path; r = context_installdb_acquire_fd(c, /* make= */ true); if (r < 0) @@ -336,7 +336,7 @@ static int context_installdb_process_entry( /* NB: We set CHASE_PROHIBIT_SYMLINKS because the path was normalized by the writer of the entry * already, and if it isn't anymore, then something is fishy. */ - _cleanup_close_ int dir_fd = chase_and_open(path, arg_root, CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, /* ret_path= */ NULL); + _cleanup_close_ int dir_fd = chase_and_open(path, c->root, CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT|CHASE_PROHIBIT_SYMLINKS, O_DIRECTORY|O_CLOEXEC, /* ret_path= */ NULL); if (dir_fd == -ENOENT) { log_debug("Install database path '%s' does not exist, expunging database entry.", path); return 0; @@ -352,30 +352,23 @@ static int context_installdb_process_entry( return context_installdb_process_directory(c, path, /* relpath= */ NULL, dir_fd, de, pattern); } -int installdb_cleanup_component(const char *node, const char *component) { +int installdb_cleanup_component(Context *context) { int r; - _cleanup_(context_freep) Context* context = NULL; - r = context_make_offline( - &context, - node, - component, - /* read_definitions_flags= */ 0); - if (r < 0) - return r; + assert(context); r = context_installdb_acquire_fd(context, /* make= */ false); if (r < 0) return r; if (r == 0) { - log_debug("Not cleaning up component '%s', install database is empty.", strna(component)); + log_debug("Not cleaning up component '%s', install database is empty.", strna(context->component)); return 0; } _cleanup_free_ DirectoryEntries *de = NULL; r = readdir_all(context->installdb_fd, RECURSE_DIR_ENSURE_TYPE|RECURSE_DIR_MUST_BE_SYMLINK, &de); if (r < 0) - return log_error_errno(r, "Failed to enumerate install database for component '%s': %m", strna(component)); + return log_error_errno(r, "Failed to enumerate install database for component '%s': %m", strna(context->component)); int ret = 0; FOREACH_ARRAY(_d, de->entries, de->n_entries) { @@ -406,14 +399,15 @@ int installdb_cleanup_component(const char *node, const char *component) { return ret; } -int installdb_list_components(char ***ret) { +int installdb_list_components(Context *context, char ***ret) { int r; + assert(context); assert(ret); _cleanup_close_ int dir_fd = chase_and_open( "/var/lib/systemd/sysupdate", - arg_root, + context->root, CHASE_MUST_BE_DIRECTORY|CHASE_PREFIX_ROOT, O_DIRECTORY|O_CLOEXEC, /* ret_path= */ NULL); diff --git a/src/sysupdate/sysupdate-cleanup.h b/src/sysupdate/sysupdate-cleanup.h index 98fbb0a83b..d7b261e35b 100644 --- a/src/sysupdate/sysupdate-cleanup.h +++ b/src/sysupdate/sysupdate-cleanup.h @@ -5,5 +5,5 @@ int context_installdb_record(Context *c, const char *path, char **patterns); -int installdb_cleanup_component(const char *node, const char *component); -int installdb_list_components(char ***ret); +int installdb_cleanup_component(Context *context); +int installdb_list_components(Context *context, char ***ret); diff --git a/src/sysupdate/sysupdate-feature.c b/src/sysupdate/sysupdate-feature.c index 795d4ef90a..0c9bb112fe 100644 --- a/src/sysupdate/sysupdate-feature.c +++ b/src/sysupdate/sysupdate-feature.c @@ -54,6 +54,7 @@ static int config_parse_url_specifiers( void *data, void *userdata) { char **s = ASSERT_PTR(data); + const char *root = ASSERT_PTR(userdata); _cleanup_free_ char *resolved = NULL; int r; @@ -64,7 +65,7 @@ static int config_parse_url_specifiers( return 0; } - r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, specifier_table, root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue); @@ -80,7 +81,7 @@ static int config_parse_url_specifiers( return free_and_replace(*s, resolved); } -int feature_read_definition(Feature *f, const char *path, const char *const *dirs) { +int feature_read_definition(Feature *f, const char *root, const char *path, const char *const *dirs) { assert(f); ConfigTableItem table[] = { @@ -105,14 +106,14 @@ int feature_read_definition(Feature *f, const char *path, const char *const *dir STRV_MAKE_CONST(path), dirs, strjoina(filename, ".d"), - arg_root, + root, /* root_fd= */ -EBADF, "Feature\0", config_item_table_lookup, table, CONFIG_PARSE_WARN, - /* userdata= */ NULL, - /* stats_by_path= */ NULL, - /* drop_in_files= */ NULL); + (void *) root, + /* ret_stats_by_path= */ NULL, + /* ret_drop_in_files= */ NULL); if (r < 0) return r; diff --git a/src/sysupdate/sysupdate-feature.h b/src/sysupdate/sysupdate-feature.h index b42df5c27e..7d7495087c 100644 --- a/src/sysupdate/sysupdate-feature.h +++ b/src/sysupdate/sysupdate-feature.h @@ -22,4 +22,4 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Feature*, feature_unref); extern const struct hash_ops feature_hash_ops; -int feature_read_definition(Feature *f, const char *path, const char *const *conf_file_dirs); +int feature_read_definition(Feature *f, const char *root, const char *path, const char *const *conf_file_dirs); diff --git a/src/sysupdate/sysupdate-target.c b/src/sysupdate/sysupdate-target.c new file mode 100644 index 0000000000..ef132960f3 --- /dev/null +++ b/src/sysupdate/sysupdate-target.c @@ -0,0 +1,22 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ + +#include "string-table.h" +#include "sysupdate-target.h" + +static const char* const target_class_table[_TARGET_CLASS_MAX] = { + [TARGET_MACHINE] = "machine", + [TARGET_PORTABLE] = "portable", + [TARGET_SYSEXT] = "sysext", + [TARGET_CONFEXT] = "confext", + [TARGET_COMPONENT] = "component", + [TARGET_HOST] = "host", +}; + +DEFINE_STRING_TABLE_LOOKUP(target_class, TargetClass); + +void target_identifier_done(TargetIdentifier *t) { + assert(t); + + t->class = _TARGET_CLASS_INVALID; + t->name = mfree(t->name); +} diff --git a/src/sysupdate/sysupdate-target.h b/src/sysupdate/sysupdate-target.h new file mode 100644 index 0000000000..9c0a13bf4a --- /dev/null +++ b/src/sysupdate/sysupdate-target.h @@ -0,0 +1,32 @@ +/* SPDX-License-Identifier: LGPL-2.1-or-later */ +#pragma once + +#include "os-util.h" + +typedef enum TargetClass { + /* These should try to match ImageClass from src/basic/os-util.h */ + TARGET_MACHINE = IMAGE_MACHINE, + TARGET_PORTABLE = IMAGE_PORTABLE, + TARGET_SYSEXT = IMAGE_SYSEXT, + TARGET_CONFEXT = IMAGE_CONFEXT, + _TARGET_CLASS_IS_IMAGE_CLASS_MAX, + + /* sysupdate-specific classes */ + TARGET_HOST = _TARGET_CLASS_IS_IMAGE_CLASS_MAX, + TARGET_COMPONENT, + + _TARGET_CLASS_MAX, + _TARGET_CLASS_INVALID = -EINVAL, +} TargetClass; + +/* Let's ensure when the number of classes is updated things are updated here too */ +assert_cc((int) _IMAGE_CLASS_MAX == (int) _TARGET_CLASS_IS_IMAGE_CLASS_MAX); + +DECLARE_STRING_TABLE_LOOKUP(target_class, TargetClass); + +typedef struct TargetIdentifier { + TargetClass class; + char *name; +} TargetIdentifier; + +void target_identifier_done(TargetIdentifier *t); diff --git a/src/sysupdate/sysupdate-transfer.c b/src/sysupdate/sysupdate-transfer.c index f7e257e041..c79743cad6 100644 --- a/src/sysupdate/sysupdate-transfer.c +++ b/src/sysupdate/sysupdate-transfer.c @@ -122,11 +122,12 @@ static int config_parse_protect_version( _cleanup_free_ char *resolved = NULL; char ***protected_versions = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); int r; assert(rvalue); - r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in ProtectVersion=, ignoring: %s", rvalue); @@ -160,11 +161,12 @@ static int config_parse_min_version( _cleanup_free_ char *resolved = NULL; char **version = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); int r; assert(rvalue); - r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in MinVersion=, ignoring: %s", rvalue); @@ -192,6 +194,7 @@ static int config_parse_url_specifiers( void *data, void *userdata) { char ***s = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); _cleanup_free_ char *resolved = NULL; int r; @@ -202,7 +205,7 @@ static int config_parse_url_specifiers( return 0; } - r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in %s=, ignoring: %s", lvalue, rvalue); @@ -236,11 +239,12 @@ static int config_parse_current_symlink( _cleanup_free_ char *resolved = NULL; char **current_symlink = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); int r; assert(rvalue); - r = specifier_printf(rvalue, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in CurrentSymlink=, ignoring: %s", rvalue); @@ -307,6 +311,7 @@ static int config_parse_resource_pattern( void *userdata) { char ***patterns = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); int r; assert(rvalue); @@ -328,7 +333,7 @@ static int config_parse_resource_pattern( if (r == 0) break; - r = specifier_printf(word, NAME_MAX, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(word, NAME_MAX, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in MatchPattern=, ignoring: %s", rvalue); @@ -361,6 +366,7 @@ static int config_parse_resource_path( void *userdata) { _cleanup_free_ char *resolved = NULL; Resource *rr = ASSERT_PTR(data); + Transfer *t = ASSERT_PTR(userdata); int r; assert(rvalue); @@ -371,7 +377,7 @@ static int config_parse_resource_path( return 0; } - r = specifier_printf(rvalue, PATH_MAX-1, specifier_table, arg_root, NULL, &resolved); + r = specifier_printf(rvalue, PATH_MAX-1, specifier_table, t->context->root, NULL, &resolved); if (r < 0) { log_syntax(unit, LOG_WARNING, filename, line, r, "Failed to expand specifiers in Path=, ignoring: %s", rvalue); @@ -549,16 +555,16 @@ int transfer_read_definition(Transfer *t, const char *path, const char **dirs, H STRV_MAKE_CONST(path), dirs, strjoina(filename, ".d"), - arg_root, + t->context->root, /* root_fd= */ -EBADF, "Transfer\0" "Source\0" "Target\0", config_item_table_lookup, table, CONFIG_PARSE_WARN, - /* userdata= */ NULL, - /* stats_by_path= */ NULL, - /* drop_in_files= */ NULL); + t, + /* ret_stats_by_path= */ NULL, + /* ret_drop_in_files= */ NULL); if (r < 0) return r; @@ -613,7 +619,7 @@ int transfer_read_definition(Transfer *t, const char *path, const char **dirs, H return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "Source specification lacks Path=."); - if (t->source.path_relative_to == PATH_RELATIVE_TO_EXPLICIT && !arg_transfer_source) + if (t->source.path_relative_to == PATH_RELATIVE_TO_EXPLICIT && !t->context->transfer_source) return log_syntax(NULL, LOG_ERR, path, 1, SYNTHETIC_ERRNO(EINVAL), "PathRelativeTo=explicit requires --transfer-source= to be specified."); @@ -682,7 +688,7 @@ int transfer_resolve_paths( assert(t); - r = resource_resolve_path(&t->source, root, arg_transfer_source, node); + r = resource_resolve_path(&t->source, root, t->context->transfer_source, node); if (r < 0) return r; @@ -849,7 +855,7 @@ int transfer_vacuum( /* Second, calculate how many instances to keep, based on the instance limit — but keep at least one */ - instances_max = arg_instances_max != UINT64_MAX ? arg_instances_max : t->instances_max; + instances_max = t->context->instances_max != UINT64_MAX ? t->context->instances_max : t->instances_max; assert(instances_max >= 1); if (instances_max == UINT64_MAX) /* Keep infinite instances? */ limit = UINT64_MAX; @@ -1369,7 +1375,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra SYSTEMD_IMPORT_PATH, "raw", "--direct", /* just copy/unpack the specified file, don't do anything else */ - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", i->path, t->temporary_partial_path), t, i, cb, userdata); @@ -1386,7 +1392,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra "--direct", /* just copy/unpack the specified file, don't do anything else */ "--offset", offset, "--size-max", max_size, - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", i->path, t->target.path), t, i, cb, userdata); @@ -1409,7 +1415,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra SYSTEMD_IMPORT_FS_PATH, "run", "--direct", /* just untar the specified file, don't do anything else */ - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", i->path, t->temporary_partial_path), @@ -1426,7 +1432,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra SYSTEMD_IMPORT_PATH, "tar", "--direct", /* just untar the specified file, don't do anything else */ - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", i->path, t->temporary_partial_path), @@ -1447,7 +1453,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra "raw", "--direct", /* just download the specified URL, don't download anything else */ "--verify", digest, /* validate by explicit SHA256 sum */ - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", i->path, t->temporary_partial_path), t, i, cb, userdata); @@ -1465,7 +1471,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra "--verify", digest, /* validate by explicit SHA256 sum */ "--offset", offset, "--size-max", max_size, - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", i->path, t->target.path), t, i, cb, userdata); @@ -1487,7 +1493,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra "--direct", /* just download the specified URL, don't download anything else */ "--verify", digest, /* validate by explicit SHA256 sum */ t->target.type == RESOURCE_SUBVOLUME ? "--btrfs-subvol=yes" : "--btrfs-subvol=no", - arg_sync ? "--sync=yes" : "--sync=no", + t->context->sync ? "--sync=yes" : "--sync=no", i->path, t->temporary_partial_path), t, i, cb, userdata); @@ -1528,7 +1534,7 @@ int transfer_acquire_instance(Transfer *t, Instance *i, InstanceMetadata *f, Tra } /* Synchronize */ - if (arg_sync && need_sync) { + if (t->context->sync && need_sync) { if (t->target.type == RESOURCE_REGULAR_FILE) r = fsync_path_and_parent_at(AT_FDCWD, t->temporary_partial_path); else { diff --git a/src/sysupdate/sysupdate.c b/src/sysupdate/sysupdate.c index 9f78ee78bf..d3874c377d 100644 --- a/src/sysupdate/sysupdate.c +++ b/src/sysupdate/sysupdate.c @@ -2,13 +2,16 @@ #include +#include "sd-bus.h" #include "sd-daemon.h" #include "sd-json.h" #include "sd-varlink.h" #include "build.h" +#include "bus-polkit.h" #include "conf-files.h" #include "constants.h" +#include "discover-image.h" #include "dissect-image.h" #include "env-util.h" #include "errno-util.h" @@ -19,6 +22,7 @@ #include "help-util.h" #include "hexdecoct.h" #include "image-policy.h" +#include "json-util.h" #include "loop-util.h" #include "main-func.h" #include "mount-util.h" @@ -28,6 +32,7 @@ #include "parse-argument.h" #include "parse-util.h" #include "pretty-print.h" +#include "runtime-scope.h" #include "sort-util.h" #include "specifier.h" #include "string-util.h" @@ -36,19 +41,21 @@ #include "sysupdate-cleanup.h" #include "sysupdate-feature.h" #include "sysupdate-instance.h" +#include "sysupdate-target.h" #include "sysupdate-transfer.h" #include "sysupdate-update-set.h" #include "sysupdate-util.h" +#include "varlink-io.systemd.SysUpdate.h" #include "varlink-util.h" #include "verbs.h" static char *arg_definitions = NULL; -bool arg_sync = true; -uint64_t arg_instances_max = UINT64_MAX; +static bool arg_sync = true; +static uint64_t arg_instances_max = UINT64_MAX; static sd_json_format_flags_t arg_json_format_flags = SD_JSON_FORMAT_OFF; static PagerFlags arg_pager_flags = 0; static bool arg_legend = true; -char *arg_root = NULL; +static char *arg_root = NULL; static char *arg_image = NULL; static bool arg_reboot = false; static int arg_cleanup = -1; @@ -57,7 +64,8 @@ static bool arg_component_all = false; static int arg_verify = -1; static ImagePolicy *arg_image_policy = NULL; static bool arg_offline = false; -char *arg_transfer_source = NULL; +static char *arg_transfer_source = NULL; +static bool arg_varlink = false; STATIC_DESTRUCTOR_REGISTER(arg_definitions, freep); STATIC_DESTRUCTOR_REGISTER(arg_root, freep); @@ -72,43 +80,108 @@ const Specifier specifier_table[] = { {} }; -Context* context_free(Context *c) { - if (!c) - return NULL; +#define CONTEXT_NULL \ + (Context) { \ + .sync = true, \ + .instances_max = UINT64_MAX, \ + .verify = -1, \ + .cleanup = -1, \ + .installdb_fd = -EBADF, \ + .target_identifier.class = _TARGET_CLASS_INVALID, \ + } - free(c->component); +void context_done(Context *c) { + assert(c); + + c->mounted_dir = umount_and_rmdir_and_free(c->mounted_dir); + c->loop_device = loop_device_unref(c->loop_device); FOREACH_ARRAY(tr, c->transfers, c->n_transfers) transfer_free(*tr); - free(c->transfers); + c->transfers = mfree(c->transfers); + c->n_transfers = 0; FOREACH_ARRAY(tr, c->disabled_transfers, c->n_disabled_transfers) transfer_free(*tr); - free(c->disabled_transfers); + c->disabled_transfers = mfree(c->disabled_transfers); + c->n_disabled_transfers = 0; - hashmap_free(c->features); + c->features = hashmap_free(c->features); FOREACH_ARRAY(us, c->update_sets, c->n_update_sets) update_set_free(*us); - free(c->update_sets); + c->update_sets = mfree(c->update_sets); + c->n_update_sets = 0; - hashmap_free(c->web_cache); + c->web_cache = hashmap_free(c->web_cache); - safe_close(c->installdb_fd); + c->installdb_fd = safe_close(c->installdb_fd); - return mfree(c); + c->definitions = mfree(c->definitions); + c->root = mfree(c->root); + c->image = mfree(c->image); + c->component = mfree(c->component); + c->image_policy = image_policy_free(c->image_policy); + c->transfer_source = mfree(c->transfer_source); + + target_identifier_done(&c->target_identifier); } -static Context* context_new(void) { - Context *c = new(Context, 1); - if (!c) - return NULL; +static int context_from_cmdline(Context *ret) { + assert(ret); - *c = (Context) { - .installdb_fd = -EBADF, - }; + _cleanup_(context_done) Context context = CONTEXT_NULL; - return c; + context.instances_max = arg_instances_max; + context.sync = arg_sync; + context.reboot = arg_reboot; + context.verify = arg_verify; + context.offline = arg_offline; + context.cleanup = arg_cleanup; + context.component_all = arg_component_all; + + if (strdup_to(&context.definitions, arg_definitions) < 0) + return log_oom(); + + if (strdup_to(&context.root, arg_root) < 0) + return log_oom(); + + if (strdup_to(&context.image, arg_image) < 0) + return log_oom(); + + if (strdup_to(&context.component, arg_component) < 0) + return log_oom(); + + if (strdup_to(&context.transfer_source, arg_transfer_source) < 0) + return log_oom(); + + if (arg_image_policy) { + context.image_policy = image_policy_copy(arg_image_policy); + if (!context.image_policy) + return log_oom(); + } + + *ret = TAKE_GENERIC(context, Context, CONTEXT_NULL); + return 0; +} + +/* Stores any long-running server state which needs to persist between varlink calls, such as state for + * pending polkit requests */ +typedef struct Server { + sd_bus *system_bus; + Hashmap *polkit_registry; +} Server; + +#define SERVER_NULL \ + (Server) { \ + /* all fields fine with being initialised to NULL */ \ + } + +static void server_done(Server *s) { + assert(s); + + s->polkit_registry = hashmap_free(s->polkit_registry); + s->system_bus = sd_bus_flush_close_unref(s->system_bus); } static DEFINE_POINTER_ARRAY_FREE_FUNC(Transfer*, transfer_free); @@ -132,7 +205,7 @@ static int read_definitions( assert(dirs); assert(suffix); - r = conf_files_list_strv_full(suffix, arg_root, + r = conf_files_list_strv_full(suffix, c->root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN, dirs, &files, &n_files); if (r < 0) @@ -151,7 +224,7 @@ static int read_definitions( if (r < 0) return r; - r = transfer_resolve_paths(t, arg_root, node); + r = transfer_resolve_paths(t, c->root, node); if (r < 0) return r; @@ -171,14 +244,19 @@ static int read_definitions( return 0; } +typedef enum ReadDefinitionsFlags { + READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0, + READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS = 1 << 1, +} ReadDefinitionsFlags; + static int context_read_definitions(Context *c, const char* node, ReadDefinitionsFlags flags) { _cleanup_strv_free_ char **dirs = NULL; int r; assert(c); - if (arg_definitions) - dirs = strv_new(arg_definitions); + if (c->definitions) + dirs = strv_new(c->definitions); else if (c->component) { char **l = CONF_PATHS_STRV(""); size_t i = 0; @@ -206,7 +284,7 @@ static int context_read_definitions(Context *c, const char* node, ReadDefinition CLEANUP_ARRAY(files, n_files, conf_file_free_array); - r = conf_files_list_strv_full(".feature", arg_root, + r = conf_files_list_strv_full(".feature", c->root, CONF_FILES_REGULAR|CONF_FILES_FILTER_MASKED|CONF_FILES_WARN, (const char**) dirs, &files, &n_files); if (r < 0) @@ -220,7 +298,7 @@ static int context_read_definitions(Context *c, const char* node, ReadDefinition if (!f) return log_oom(); - r = feature_read_definition(f, e->result, (const char**) dirs); + r = feature_read_definition(f, c->root, e->result, (const char**) dirs); if (r < 0) return r; @@ -270,7 +348,7 @@ static int context_load_installed_instances(Context *c) { r = resource_load_instances( &t->target, - arg_verify >= 0 ? arg_verify : t->verify, + c->verify >= 0 ? c->verify : t->verify, &c->web_cache); if (r < 0) return r; @@ -281,7 +359,7 @@ static int context_load_installed_instances(Context *c) { r = resource_load_instances( &t->target, - arg_verify >= 0 ? arg_verify : t->verify, + c->verify >= 0 ? c->verify : t->verify, &c->web_cache); if (r < 0) return r; @@ -302,7 +380,7 @@ static int context_load_available_instances(Context *c) { r = resource_load_instances( &t->source, - arg_verify >= 0 ? arg_verify : t->verify, + c->verify >= 0 ? c->verify : t->verify, &c->web_cache); if (r < 0) return r; @@ -526,7 +604,7 @@ static int context_discover_update_sets(Context *c) { if (r < 0) return r; - if (!arg_offline) { + if (!c->offline) { log_info("Determining available update sets%s", glyph(GLYPH_ELLIPSIS)); r = context_discover_update_sets_by_flag(c, UPDATE_AVAILABLE); @@ -926,28 +1004,73 @@ static int context_vacuum( return 0; } -int context_make_offline( - Context **ret, - const char *node, - const char *component, - ReadDefinitionsFlags read_definitions_flags) { - _cleanup_(context_freep) Context* context = NULL; +typedef enum ProcessImageFlags { + PROCESS_IMAGE_READ_ONLY = 1 << 0, +} ProcessImageFlags; + +static int process_image( + Context *c, + ProcessImageFlags flags) { + + _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; + _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; int r; - assert(ret); + assert(c); - /* Allocates a context object and initializes everything we can initialize offline, i.e. without - * checking on the update source (i.e. the Internet) what versions are available */ + if (!c->image) + return 0; - context = context_new(); - if (!context) - return log_oom(); + assert(!c->root); + assert(!c->mounted_dir); + assert(!c->loop_device); - r = free_and_strdup_warn(&context->component, component); + r = mount_image_privately_interactively( + c->image, + c->image_policy, + (FLAGS_SET(flags, PROCESS_IMAGE_READ_ONLY) ? DISSECT_IMAGE_READ_ONLY : 0) | + DISSECT_IMAGE_FSCK | + DISSECT_IMAGE_MKDIR | + DISSECT_IMAGE_GROWFS | + DISSECT_IMAGE_RELAX_VAR_CHECK | + DISSECT_IMAGE_USR_NO_ROOT | + DISSECT_IMAGE_GENERIC_ROOT | + DISSECT_IMAGE_REQUIRE_ROOT | + DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, + &mounted_dir, + /* ret_dir_fd= */ NULL, + &loop_device); if (r < 0) return r; - r = context_read_definitions(context, node, read_definitions_flags); + c->root = strdup(mounted_dir); + if (!c->root) + return log_oom(); + + c->mounted_dir = TAKE_PTR(mounted_dir); + c->loop_device = TAKE_PTR(loop_device); + + return 0; +} + +static int context_list_components(Context *context, char ***component_names, bool *has_default_component); + +static int context_load_offline( + Context *context, + ProcessImageFlags process_image_flags, + ReadDefinitionsFlags read_definitions_flags) { + int r; + + assert(context); + + /* Sets up a context object and initializes everything we can initialize offline, i.e. without + * checking on the update source (i.e. the Internet) what versions are available */ + + r = process_image(context, process_image_flags); + if (r < 0) + return r; + + r = context_read_definitions(context, context->loop_device ? context->loop_device->node : NULL, read_definitions_flags); if (r < 0) return r; @@ -955,32 +1078,27 @@ int context_make_offline( if (r < 0) return r; - *ret = TAKE_PTR(context); return 0; } -static int context_make_online( - Context **ret, - const char *node, - const char *component) { - - _cleanup_(context_freep) Context* context = NULL; +static int context_load_online( + Context *context, + ProcessImageFlags process_image_flags) { int r; - assert(ret); + assert(context); - /* Like context_make_offline(), but also communicates with the update source looking for new + /* Like context_load_offline(), but also communicates with the update source looking for new * versions (as long as --offline is not specified on the command line). */ - r = context_make_offline( - &context, - node, - component, + r = context_load_offline( + context, + process_image_flags, READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS); if (r < 0) return r; - if (!arg_offline) { + if (!context->offline) { r = context_load_available_instances(context); if (r < 0) return r; @@ -990,10 +1108,147 @@ static int context_make_online( if (r < 0) return r; - *ret = TAKE_PTR(context); return 0; } +static bool image_type_can_sysupdate(ImageType image_type) { + switch (image_type) { + case IMAGE_DIRECTORY: + case IMAGE_SUBVOLUME: + case IMAGE_RAW: + case IMAGE_BLOCK: + return true; + + /* systemd-sysupdate doesn't support mstack images yet */ + case IMAGE_MSTACK: + default: + return false; + } +} + +static int context_load_paths_from_image(Context *context, Image *image) { + assert(context); + assert(image); + + assert(!context->root); + assert(!context->image); + + switch (image->type) { + case IMAGE_DIRECTORY: + case IMAGE_SUBVOLUME: + context->root = strdup(image->path); + if (!context->root) + return log_oom(); + return 0; + case IMAGE_RAW: + case IMAGE_BLOCK: + context->image = strdup(image->path); + if (!context->image) + return log_oom(); + return 0; + default: + assert_not_reached(); + } +} + +/* Load a Context to point to the target given by the TargetIdentifier. The TargetIdentifier will have been + * 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) { + int r; + + assert(context); + assert(context->target_identifier.class != _TARGET_CLASS_INVALID); + + /* These shouldn’t have been set up some other way first */ + assert(!context->component); + assert(!context->root); + assert(!context->image); + + switch (context->target_identifier.class) { + case TARGET_MACHINE: + case TARGET_PORTABLE: + case TARGET_SYSEXT: + case TARGET_CONFEXT: { + _cleanup_hashmap_free_ Hashmap *images = NULL; + Image *image, *selected_image = NULL; + + /* These are all image-based target classes, so first find the corresponding image. */ + r = image_discover(RUNTIME_SCOPE_SYSTEM, (ImageClass) context->target_identifier.class, NULL, &images); + if (r < 0) + return r; + + HASHMAP_FOREACH(image, images) { + bool have = false; + _cleanup_(context_done) Context image_context = CONTEXT_NULL; + + if (image_is_host(image)) + continue; /* We already enroll the host ourselves */ + + if (!image_type_can_sysupdate(image->type)) + continue; + + if (!streq(image->name, context->target_identifier.name)) + continue; + + r = context_load_paths_from_image(&image_context, image); + if (r < 0) + return r; + + /* Load the components in a separate Context specific to the given Image before + * committing to loading that state to the main Context. */ + r = context_load_offline(&image_context, 0, 0); + if (r < 0) + return r; + + r = context_list_components(&image_context, /* component_names= */ NULL, &have); + if (r < 0) + return r; + if (!have) { + log_debug("Skipping %s because it has no default component", image->path); + continue; + } + + /* This is the match we were looking for */ + selected_image = image; + break; + } + + if (!selected_image) + return -ENOENT; + + r = context_load_paths_from_image(context, selected_image); + if (r < 0) + return r; + + break; + } + case TARGET_HOST: + /* No additional setup needed */ + break; + case TARGET_COMPONENT: { + _cleanup_strv_free_ char **component_names = NULL; + + r = context_list_components(context, &component_names, /* has_default_component= */ NULL); + if (r < 0) + return r; + + if (!strv_contains(component_names, context->target_identifier.name)) + return -ENOENT; + + context->component = strdup(context->target_identifier.name); + if (!context->component) + return log_oom(); + break; + } + default: + assert_not_reached(); + } + + return context_load_online(context, process_image_flags); +} + static int context_on_acquire_progress(const Transfer *t, const Instance *inst, unsigned percentage) { const Context *c = ASSERT_PTR(t->context); size_t i, n = c->n_transfers; @@ -1101,7 +1356,7 @@ static int context_acquire( if (r < 0) return r; - if (arg_sync) + if (c->sync) sync(); (void) sd_notifyf(/* unset_environment= */ false, @@ -1126,7 +1381,7 @@ static int context_acquire( return r; } - if (arg_sync) + if (c->sync) sync(); return 1; @@ -1249,7 +1504,7 @@ static int context_notify_subscribers(Context *c, UpdateSet *us) { _cleanup_(sd_json_variant_unrefp) sd_json_variant *params = NULL; r = sd_json_buildo( ¶ms, - SD_JSON_BUILD_PAIR_CONDITION(!!arg_component, "component", SD_JSON_BUILD_STRING(arg_component)), + SD_JSON_BUILD_PAIR_CONDITION(!!c->component, "component", SD_JSON_BUILD_STRING(c->component)), SD_JSON_BUILD_PAIR_CONDITION(!!us, "version", SD_JSON_BUILD_STRING(us ? us->version : NULL)), SD_JSON_BUILD_PAIR_CONDITION(!!resources, "resources", SD_JSON_BUILD_VARIANT(resources))); if (r < 0) @@ -1309,14 +1564,14 @@ static int context_install( !inst->is_pending) continue; - r = transfer_install_instance(t, inst, arg_root); + r = transfer_install_instance(t, inst, c->root); if (r < 0) return r; } log_info("%s Successfully installed update '%s'.", glyph(GLYPH_SPARKLES), us->version); - if (!arg_root) + if (!c->root) (void) context_notify_subscribers(c, us); (void) sd_notifyf(/* unset_environment= */ false, @@ -1328,57 +1583,58 @@ static int context_install( return 1; } -static int process_image( - bool ro, - char **ret_mounted_dir, - LoopDevice **ret_loop_device) { +static JSON_DISPATCH_ENUM_DEFINE(dispatch_target_class, TargetClass, target_class_from_string); - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_freep) char *mounted_dir = NULL; +static int dispatch_target_identifier(const char *name, sd_json_variant *variant, sd_json_dispatch_flags_t flags, void *userdata) { + TargetIdentifier *t = ASSERT_PTR(userdata); + static const sd_json_dispatch_field dispatch[] = { + { "class", SD_JSON_VARIANT_STRING, dispatch_target_class, voffsetof(*t, class), SD_JSON_MANDATORY }, + { "name", SD_JSON_VARIANT_STRING, sd_json_dispatch_string, voffsetof(*t, name), SD_JSON_NULLABLE }, + {} + }; int r; - assert(ret_mounted_dir); - assert(ret_loop_device); - - if (!arg_image) - return 0; - - assert(!arg_root); - - r = mount_image_privately_interactively( - arg_image, - arg_image_policy, - (ro ? DISSECT_IMAGE_READ_ONLY : 0) | - DISSECT_IMAGE_FSCK | - DISSECT_IMAGE_MKDIR | - DISSECT_IMAGE_GROWFS | - DISSECT_IMAGE_RELAX_VAR_CHECK | - DISSECT_IMAGE_USR_NO_ROOT | - DISSECT_IMAGE_GENERIC_ROOT | - DISSECT_IMAGE_REQUIRE_ROOT | - DISSECT_IMAGE_ALLOW_USERSPACE_VERITY, - &mounted_dir, - /* ret_dir_fd= */ NULL, - &loop_device); + r = sd_json_dispatch(variant, dispatch, flags, t); if (r < 0) return r; - arg_root = strdup(mounted_dir); - if (!arg_root) - return log_oom(); + /* Name is mandatory unless class is `host` */ + if ((t->class == TARGET_HOST) != (!t->name)) + return json_log(variant, flags, SYNTHETIC_ERRNO(ENXIO), "Target name does not match class."); - *ret_mounted_dir = TAKE_PTR(mounted_dir); - *ret_loop_device = TAKE_PTR(loop_device); + if (t->class == TARGET_COMPONENT && !component_name_valid(t->name)) + return json_log(variant, flags, SYNTHETIC_ERRNO(EINVAL), "Component name invalid: %s", t->name); return 0; } +static int verify_polkit(Context *context, sd_varlink *link, const char *action, const char **details) { + int r; + Server *s = ASSERT_PTR(sd_varlink_get_userdata(ASSERT_PTR(link))); + + assert(context); + + if (!s->system_bus) { + r = sd_bus_open_system_with_description(&s->system_bus, "sysupdate-system"); + if (r < 0) + return log_error_errno(r, "Failed to get system bus connection: %m"); + + r = sd_bus_attach_event(s->system_bus, sd_varlink_get_event(link), SD_EVENT_PRIORITY_NORMAL); + if (r < 0) + return log_error_errno(r, "Failed to attach system bus to event loop: %m"); + } + + return varlink_verify_polkit_async(link, + s->system_bus, + action, + details, + &s->polkit_registry); +} + VERB(verb_list, "list", "[VERSION]", VERB_ANY, 2, VERB_DEFAULT, "Show installed and available versions"); static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; - _cleanup_(context_freep) Context* context = NULL; + _cleanup_(context_done) Context context = CONTEXT_NULL; _cleanup_strv_free_ char **appstream_urls = NULL; const char *version; int r; @@ -1386,31 +1642,28 @@ static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { assert(argc <= 2); version = argc >= 2 ? argv[1] : NULL; - if (arg_component_all) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); - - r = process_image(/* ro= */ true, &mounted_dir, &loop_device); + r = context_from_cmdline(&context); if (r < 0) return r; - r = context_make_online( - &context, - loop_device ? loop_device->node : NULL, - arg_component); + 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); if (r < 0) return r; if (version) - return context_show_version(context, version); + return context_show_version(&context, version); else if (!sd_json_format_enabled(arg_json_format_flags)) - return context_show_table(context); + return context_show_table(&context); else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; _cleanup_strv_free_ char **versions = NULL; const char *current = NULL; bool current_is_pending = false; - FOREACH_ARRAY(update_set, context->update_sets, context->n_update_sets) { + FOREACH_ARRAY(update_set, context.update_sets, context.n_update_sets) { UpdateSet *us = *update_set; if (FLAGS_SET(us->flags, UPDATE_INSTALLED) && @@ -1424,7 +1677,7 @@ static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { return log_oom(); } - FOREACH_ARRAY(tr, context->transfers, context->n_transfers) + FOREACH_ARRAY(tr, context.transfers, context.n_transfers) STRV_FOREACH(appstream_url, (*tr)->appstream) { /* Avoid duplicates */ if (strv_contains(appstream_urls, *appstream_url)) @@ -1452,9 +1705,7 @@ static int verb_list(int argc, char *argv[], uintptr_t _data, void *userdata) { VERB(verb_features, "features", "[FEATURE]", VERB_ANY, 2, 0, "Show optional features"); static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; - _cleanup_(context_freep) Context* context = NULL; + _cleanup_(context_done) Context context = CONTEXT_NULL; _cleanup_(table_unrefp) Table *table = NULL; const char *feature_id; Feature *f; @@ -1463,17 +1714,16 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata assert(argc <= 2); feature_id = argc >= 2 ? argv[1] : NULL; - if (arg_component_all) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); - - r = process_image(/* ro= */ true, &mounted_dir, &loop_device); + r = context_from_cmdline(&context); if (r < 0) return r; - r = context_make_offline( + if (context.component_all) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); + + r = context_load_offline( &context, - loop_device ? loop_device->node : NULL, - arg_component, + PROCESS_IMAGE_READ_ONLY, READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS); if (r < 0) return r; @@ -1481,7 +1731,7 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata if (feature_id) { _cleanup_strv_free_ char **transfers = NULL; - f = hashmap_get(context->features, feature_id); + f = hashmap_get(context.features, feature_id); if (!f) return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Optional feature not found: %s", @@ -1491,7 +1741,7 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata if (!table) return log_oom(); - FOREACH_ARRAY(tr, context->transfers, context->n_transfers) { + FOREACH_ARRAY(tr, context.transfers, context.n_transfers) { Transfer *t = *tr; if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id)) @@ -1502,7 +1752,7 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata return log_oom(); } - FOREACH_ARRAY(tr, context->disabled_transfers, context->n_disabled_transfers) { + FOREACH_ARRAY(tr, context.disabled_transfers, context.n_disabled_transfers) { Transfer *t = *tr; if (!strv_contains(t->features, f->id) && !strv_contains(t->requisite_features, f->id)) @@ -1557,7 +1807,7 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata if (!table) return log_oom(); - HASHMAP_FOREACH(f, context->features) { + HASHMAP_FOREACH(f, context.features) { r = table_add_many(table, TABLE_BOOLEAN_CHECKMARK, f->enabled, TABLE_SET_COLOR, ansi_highlight_green_red(f->enabled), @@ -1574,7 +1824,7 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; _cleanup_strv_free_ char **features = NULL; - HASHMAP_FOREACH(f, context->features) { + HASHMAP_FOREACH(f, context.features) { r = strv_extend(&features, f->id); if (r < 0) return log_oom(); @@ -1595,39 +1845,36 @@ static int verb_features(int argc, char *argv[], uintptr_t _data, void *userdata VERB_NOARG(verb_check_new, "check-new", "Check if there's a new version available"); static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; - _cleanup_(context_freep) Context* context = NULL; + _cleanup_(context_done) Context context = CONTEXT_NULL; int r; assert(argc <= 1); - if (arg_component_all) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); - - r = process_image(/* ro= */ true, &mounted_dir, &loop_device); + r = context_from_cmdline(&context); if (r < 0) return r; - r = context_make_online( + 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, - loop_device ? loop_device->node : NULL, - arg_component); + PROCESS_IMAGE_READ_ONLY); if (r < 0) return r; if (!sd_json_format_enabled(arg_json_format_flags)) { - if (!context->candidate) { + if (!context.candidate) { log_debug("No candidate found."); return EXIT_FAILURE; } - puts(context->candidate->version); + puts(context.candidate->version); } else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; - if (context->candidate) - r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context->candidate->version)); + if (context.candidate) + r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version)); else r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_NULL("available")); if (r < 0) @@ -1641,15 +1888,59 @@ static int verb_check_new(int argc, char *argv[], uintptr_t _data, void *userdat return EXIT_SUCCESS; } +static int vl_method_check_new(sd_varlink *link, sd_json_variant *parameters, sd_varlink_method_flags_t flags, void *userdata) { + _cleanup_(context_done) Context context = CONTEXT_NULL; + int r; + + assert(link); + + static const sd_json_dispatch_field dispatch_table[] = { + { "target", SD_JSON_VARIANT_OBJECT, dispatch_target_identifier, voffsetof(context, target_identifier), SD_JSON_MANDATORY }, + VARLINK_DISPATCH_POLKIT_FIELD, + {}, + }; + + r = sd_varlink_dispatch(link, parameters, dispatch_table, &context); + if (r != 0) + return r; + + r = verify_polkit(&context, link, "org.freedesktop.sysupdate1.check", + (const char**) STRV_MAKE( + "class", target_class_to_string(context.target_identifier.class), + "offline", "0", + context.target_identifier.name ? "name" : NULL, context.target_identifier.name)); + if (r <= 0) + return r; + + if (getenv_bool("SYSTEMD_SYSUPDATE_NO_VERIFY") > 0) + context.verify = 0; + + /* CheckNew is always online */ + context.offline = false; + + r = context_load_online_from_target(&context, PROCESS_IMAGE_READ_ONLY); + if (r == -ENOENT) + return sd_varlink_error(link, "io.systemd.SysUpdate.NoSuchTarget", NULL); + else if (r < 0) + return r; + + if (context.candidate) + r = sd_varlink_replybo(link, SD_JSON_BUILD_PAIR_STRING("available", context.candidate->version)); + else + r = sd_varlink_error(link, "io.systemd.SysUpdate.NoUpdateNeeded", NULL); + if (r < 0) + return r; + + return 0; +} + typedef enum { UPDATE_ACTION_ACQUIRE = 1 << 0, UPDATE_ACTION_INSTALL = 1 << 1, } UpdateActionFlags; static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flags) { - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; - _cleanup_(context_freep) Context* context = NULL; + _cleanup_(context_done) Context context = CONTEXT_NULL; _cleanup_free_ char *booted_version = NULL; UpdateSet *applied = NULL; const char *version; @@ -1658,35 +1949,33 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag assert(argc <= 2); version = argc >= 2 ? argv[1] : NULL; - if (arg_component_all) + r = context_from_cmdline(&context); + if (r < 0) + return r; + + if (context.component_all) return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); - if (arg_instances_max < 2) + if (context.instances_max < 2) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --instances-max argument must be >= 2 while updating"); - if (arg_reboot) { + if (context.reboot) { /* If automatic reboot on completion is requested, let's first determine the currently booted image */ - r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version); + r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version); if (r < 0) return log_error_errno(r, "Failed to parse /etc/os-release: %m"); if (!booted_version) return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION field."); } - r = process_image(/* ro= */ false, &mounted_dir, &loop_device); - if (r < 0) - return r; - - const char *node = loop_device ? loop_device->node : NULL; bool installed = false; int ret = 0; - r = context_make_online( + r = context_load_online( &context, - node, - arg_component); + /* process_image_flags= */ 0); if (r < 0) { if (r != -ENOENT) return r; @@ -1695,14 +1984,14 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag RET_GATHER(ret, r); } else { if (action_flags & UPDATE_ACTION_ACQUIRE) - r = context_acquire(context, version); + r = context_acquire(&context, version); else - r = context_process_partial_and_pending(context, version); + r = context_process_partial_and_pending(&context, version); if (r < 0) return r; if (FLAGS_SET(action_flags, UPDATE_ACTION_INSTALL) && r > 0) { /* installation of update indicated */ - r = context_install(context, version, &applied); + r = context_install(&context, version, &applied); if (r < 0) return r; @@ -1717,17 +2006,17 @@ static int verb_update_impl(int argc, char **argv, UpdateActionFlags action_flag if (f < 0 && f != -ENXIO) log_debug_errno(f, "Failed to parse $SYSTEMD_SYSUPDATE_FORCE_NOTIFY, ignoring: %m"); if (f > 0) - (void) context_notify_subscribers(context, /* us= */ NULL); + (void) context_notify_subscribers(&context, /* us= */ NULL); } } - if (arg_cleanup > 0) - RET_GATHER(ret, installdb_cleanup_component(node, arg_component)); + if (context.cleanup > 0) + RET_GATHER(ret, installdb_cleanup_component(&context)); if (installed) { /* We installed something, yay */ - if (arg_reboot) { + if (context.reboot) { assert(applied); assert(booted_version); @@ -1766,33 +2055,30 @@ static int verb_acquire(int argc, char *argv[], uintptr_t _data, void *userdata) VERB_NOARG(verb_vacuum, "vacuum", "Make room, by deleting old versions"); static int verb_vacuum(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; - _cleanup_(context_freep) Context* context = NULL; + _cleanup_(context_done) Context context = CONTEXT_NULL; int r; assert(argc <= 1); - if (arg_component_all) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); - - if (arg_instances_max < 1) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), - "The --instances-max argument must be >= 1 while vacuuming"); - - r = process_image(/* ro= */ false, &mounted_dir, &loop_device); + r = context_from_cmdline(&context); if (r < 0) return r; - r = context_make_offline( + if (context.component_all) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); + + if (context.instances_max < 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "The --instances-max argument must be >= 1 while vacuuming"); + + r = context_load_offline( &context, - loop_device ? loop_device->node : NULL, - arg_component, + /* process_image_flags= */ 0, READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS); if (r < 0) return r; - return context_vacuum(context, 0, NULL); + return context_vacuum(&context, 0, NULL); } VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0, @@ -1800,47 +2086,50 @@ VERB(verb_pending_or_reboot, "pending", NULL, 1, 1, 0, VERB(verb_pending_or_reboot, "reboot", NULL, 1, 1, 0, "Reboot if a newer version is installed than booted"); static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(context_freep) Context* context = NULL; + _cleanup_(context_done) Context context = CONTEXT_NULL; _cleanup_free_ char *booted_version = NULL; int r; assert(argc == 1); - if (arg_image || arg_root) + r = context_from_cmdline(&context); + if (r < 0) + return r; + + if (context.image || context.root) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --root=/--image= switches may not be combined with the '%s' operation.", argv[0]); - if (arg_component || arg_component_all) + if (context.component || context.component_all) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --component= and --component-all switches may not be combined with the '%s' operation, which only applies to the booted OS version.", argv[0]); - r = context_make_offline( + r = context_load_offline( &context, - /* node= */ NULL, - arg_component, + /* process_image_flags= */ 0, READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS|READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS); if (r < 0) return r; log_info("Determining installed update sets%s", glyph(GLYPH_ELLIPSIS)); - r = context_discover_update_sets_by_flag(context, UPDATE_INSTALLED); + r = context_discover_update_sets_by_flag(&context, UPDATE_INSTALLED); if (r < 0) return r; - if (!context->newest_installed) + if (!context.newest_installed) return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "Couldn't find any suitable installed versions."); - r = parse_os_release(arg_root, "IMAGE_VERSION", &booted_version); - if (r < 0) /* yes, arg_root is NULL here, but we have to pass something, and it's a lot more readable + r = parse_os_release(context.root, "IMAGE_VERSION", &booted_version); + if (r < 0) /* yes, context.root is NULL here, but we have to pass something, and it's a lot more readable * if we see what the first argument is about */ return log_error_errno(r, "Failed to parse /etc/os-release: %m"); if (!booted_version) return log_error_errno(SYNTHETIC_ERRNO(ENODATA), "/etc/os-release lacks IMAGE_VERSION= field."); - r = strverscmp_improved(context->newest_installed->version, booted_version); + r = strverscmp_improved(context.newest_installed->version, booted_version); if (r > 0) { log_notice("Newest installed version '%s' is newer than booted version '%s'.%s", - context->newest_installed->version, booted_version, + context.newest_installed->version, booted_version, streq(argv[0], "pending") ? " Reboot recommended." : ""); if (streq(argv[0], "reboot")) @@ -1849,10 +2138,10 @@ static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void return EXIT_SUCCESS; } else if (r == 0) log_info("Newest installed version '%s' matches booted version '%s'.", - context->newest_installed->version, booted_version); + context.newest_installed->version, booted_version); else log_warning("Newest installed version '%s' is older than booted version '%s'.", - context->newest_installed->version, booted_version); + context.newest_installed->version, booted_version); if (streq(argv[0], "pending")) /* When called as 'pending' tell the caller via failure exit code that there's nothing newer installed */ return EXIT_FAILURE; @@ -1860,47 +2149,61 @@ static int verb_pending_or_reboot(int argc, char *argv[], uintptr_t _data, void return EXIT_SUCCESS; } +static int context_list_components(Context *context, char ***component_names, bool *has_default_component) { + int r; + + assert(context); + + _cleanup_strv_free_ char **z = NULL; + r = get_component_list(context->root, &z); + if (r < 0) + return r; + + if (component_names) + *component_names = TAKE_PTR(z); + + /* Does the system have at least one transfer file in /etc/sysupdate.d, which can be considered a + * TARGET_HOST? See target_get_argument() in sysupdated.c */ + if (has_default_component) + *has_default_component = (!context->definitions && + !context->component && + !context->root && + !context->image && + context->n_transfers > 0); + + return 0; +} + VERB_NOARG(verb_components, "components", "Show list of components"); static int verb_components(int argc, char *argv[], uintptr_t _data, void *userdata) { - _cleanup_(context_freep) Context* context = NULL; - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; + _cleanup_(context_done) Context context = CONTEXT_NULL; + _cleanup_strv_free_ char **component_names = NULL; bool has_default_component = false; int r; assert(argc <= 1); - if (arg_component_all) - return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); - - r = process_image(/* ro= */ false, &mounted_dir, &loop_device); + r = context_from_cmdline(&context); if (r < 0) return r; - r = context_make_offline( + if (context.component_all) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "--component-all currently not supported for '%s'.", argv[0]); + + r = context_load_offline( &context, - loop_device ? loop_device->node : NULL, - arg_component, + /* process_image_flags= */ 0, /* read_definitions_flags= */ 0); if (r < 0) return r; - _cleanup_strv_free_ char **z = NULL; - r = get_component_list(arg_root, &z); + r = context_list_components(&context, &component_names, &has_default_component); if (r < 0) return log_error_errno(r, "Failed to enumerate components: %m"); - /* Does the system have at least one transfer file in /etc/sysupdate.d, which can be considered a - * TARGET_HOST? See target_get_argument() in sysupdated.c */ - has_default_component = (!arg_definitions && - !context->component && - !arg_root && - !arg_image && - context->n_transfers > 0); - if (!sd_json_format_enabled(arg_json_format_flags)) { - if (!has_default_component && strv_isempty(z)) { + if (!has_default_component && strv_isempty(component_names)) { log_info("No components defined."); return 0; } @@ -1909,13 +2212,13 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda printf("%s%s\n", ansi_highlight(), ansi_normal()); - STRV_FOREACH(i, z) + STRV_FOREACH(i, component_names) puts(*i); } else { _cleanup_(sd_json_variant_unrefp) sd_json_variant *json = NULL; r = sd_json_buildo(&json, SD_JSON_BUILD_PAIR_BOOLEAN("default", has_default_component), - SD_JSON_BUILD_PAIR_STRV("components", z)); + SD_JSON_BUILD_PAIR_STRV("components", component_names)); if (r < 0) return log_error_errno(r, "Failed to create JSON: %m"); @@ -1929,32 +2232,56 @@ static int verb_components(int argc, char *argv[], uintptr_t _data, void *userda VERB_NOARG(verb_cleanup, "cleanup", "Clean up orphaned files"); static int verb_cleanup(int argc, char *argv[], uintptr_t _data, void *userdata) { + _cleanup_(context_done) Context context = CONTEXT_NULL; int r; assert(argc <= 1); - if (arg_cleanup == 0) - return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invocation of 'cleanup' with --cleanup=no is contradictory, refusing."); - - _cleanup_(loop_device_unrefp) LoopDevice *loop_device = NULL; - _cleanup_(umount_and_rmdir_and_freep) char *mounted_dir = NULL; - r = process_image(/* ro= */ false, &mounted_dir, &loop_device); + r = context_from_cmdline(&context); if (r < 0) return r; - const char *node = loop_device ? loop_device->node : NULL; + if (context.cleanup == 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invocation of 'cleanup' with --cleanup=no is contradictory, refusing."); + + r = context_load_offline( + &context, + /* process_image_flags= */ 0, + /* read_definitions_flags= */ 0); + if (r < 0) + return r; int ret = 0; - RET_GATHER(ret, installdb_cleanup_component(node, arg_component)); + RET_GATHER(ret, installdb_cleanup_component(&context)); - if (arg_component_all) { + if (context.component_all) { _cleanup_strv_free_ char **z = NULL; - r = installdb_list_components(&z); + r = installdb_list_components(&context, &z); if (r < 0) return log_error_errno(r, "Failed to enumerate components: %m"); - STRV_FOREACH(i, z) - RET_GATHER(ret, installdb_cleanup_component(node, *i)); + STRV_FOREACH(i, z) { + _cleanup_(context_done) Context component_context = CONTEXT_NULL; + + r = context_from_cmdline(&component_context); + if (r < 0) + return r; + + /* Override the component with our iter. This needs to be done in a fresh Context + * as the installdb_fd and other state are specific to the component. */ + r = free_and_strdup_warn(&component_context.component, *i); + if (r < 0) + return r; + + r = context_load_offline( + &component_context, + /* process_image_flags= */ 0, + /* read_definitions_flags= */ 0); + if (r < 0) + return r; + + RET_GATHER(ret, installdb_cleanup_component(&component_context)); + } } return ret; @@ -2157,10 +2484,44 @@ static int parse_argv(int argc, char *argv[], char ***remaining_args) { if (arg_definitions && arg_component) return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "The --definitions= and --component= switches may not be combined."); + r = sd_varlink_invocation(SD_VARLINK_ALLOW_ACCEPT); + if (r < 0) + return log_error_errno(r, "Failed to check if invoked in Varlink mode: %m"); + if (r > 0) + arg_varlink = true; + *remaining_args = option_parser_get_args(&opts); return 1; } +static int vl_server(void) { + _cleanup_(sd_varlink_server_unrefp) sd_varlink_server *varlink_server = NULL; + _cleanup_(server_done) Server server = SERVER_NULL; + int r; + + r = varlink_server_new(&varlink_server, + SD_VARLINK_SERVER_ACCOUNT_UID|SD_VARLINK_SERVER_INHERIT_USERDATA, + &server); + if (r < 0) + return log_error_errno(r, "Failed to allocate Varlink server: %m"); + + r = sd_varlink_server_add_interface(varlink_server, &vl_interface_io_systemd_SysUpdate); + if (r < 0) + return log_error_errno(r, "Failed to add Varlink interface: %m"); + + r = sd_varlink_server_bind_method_many( + varlink_server, + "io.systemd.SysUpdate.CheckNew", vl_method_check_new); + if (r < 0) + return log_error_errno(r, "Failed to bind Varlink method: %m"); + + r = sd_varlink_server_loop_auto(varlink_server); + if (r < 0) + return log_error_errno(r, "Failed to run Varlink event loop: %m"); + + return 0; +} + static int run(int argc, char *argv[]) { int r; @@ -2171,6 +2532,9 @@ static int run(int argc, char *argv[]) { if (r <= 0) return r; + if (arg_varlink) + return vl_server(); /* Invocation as Varlink service */ + return dispatch_verb(args, NULL); } diff --git a/src/sysupdate/sysupdate.h b/src/sysupdate/sysupdate.h index b925d4f5a2..02a9f7c8f3 100644 --- a/src/sysupdate/sysupdate.h +++ b/src/sysupdate/sysupdate.h @@ -3,9 +3,27 @@ #include "specifier.h" #include "sysupdate-forward.h" +#include "sysupdate-target.h" typedef struct Context { + /* Parameters/Command line arguments: */ + char *definitions; + bool sync; + uint64_t instances_max; + char *root; + char *image; + bool reboot; + int cleanup; char *component; + bool component_all; + int verify; + ImagePolicy *image_policy; + bool offline; + char *transfer_source; + + /* Loaded state: */ + LoopDevice *loop_device; + char *mounted_dir; Transfer **transfers; size_t n_transfers; @@ -23,21 +41,10 @@ typedef struct Context { Hashmap *web_cache; /* Cache for downloaded resources, keyed by URL */ int installdb_fd; + + TargetIdentifier target_identifier; } Context; -Context* context_free(Context *c); -DEFINE_TRIVIAL_CLEANUP_FUNC(Context*, context_free); - -typedef enum ReadDefinitionsFlags { - READ_DEFINITIONS_REQUIRES_ENABLED_TRANSFERS = 1 << 0, - READ_DEFINITIONS_REQUIRES_ANY_TRANSFERS = 1 << 1, -} ReadDefinitionsFlags; - -int context_make_offline(Context **ret, const char *node, const char *component, ReadDefinitionsFlags read_definitions_flags); - -extern bool arg_sync; -extern uint64_t arg_instances_max; -extern char *arg_root; -extern char *arg_transfer_source; +void context_done(Context *c); extern const Specifier specifier_table[]; diff --git a/src/sysupdate/sysupdated.c b/src/sysupdate/sysupdated.c index 359fb50b91..8022fde77e 100644 --- a/src/sysupdate/sysupdated.c +++ b/src/sysupdate/sysupdated.c @@ -40,6 +40,7 @@ #include "signal-util.h" #include "string-table.h" #include "strv.h" +#include "sysupdate-target.h" #include "sysupdate-util.h" #include "utf8.h" @@ -64,25 +65,6 @@ typedef struct Manager { /* Forward declare so that jobs can call it on exit */ static void manager_check_idle(Manager *m); -typedef enum TargetClass { - /* These should try to match ImageClass from src/basic/os-util.h */ - TARGET_MACHINE = IMAGE_MACHINE, - TARGET_PORTABLE = IMAGE_PORTABLE, - TARGET_SYSEXT = IMAGE_SYSEXT, - TARGET_CONFEXT = IMAGE_CONFEXT, - _TARGET_CLASS_IS_IMAGE_CLASS_MAX, - - /* sysupdate-specific classes */ - TARGET_HOST = _TARGET_CLASS_IS_IMAGE_CLASS_MAX, - TARGET_COMPONENT, - - _TARGET_CLASS_MAX, - _TARGET_CLASS_INVALID = -EINVAL, -} TargetClass; - -/* Let's ensure when the number of classes is updated things are updated here too */ -assert_cc((int) _IMAGE_CLASS_MAX == (int) _TARGET_CLASS_IS_IMAGE_CLASS_MAX); - typedef struct Target { Manager *manager; @@ -138,17 +120,6 @@ struct Job { JobReady detach_cb; /* Callback called when job has started. Detaches the job to run in the background */ }; -static const char* const target_class_table[_TARGET_CLASS_MAX] = { - [TARGET_MACHINE] = "machine", - [TARGET_PORTABLE] = "portable", - [TARGET_SYSEXT] = "sysext", - [TARGET_CONFEXT] = "confext", - [TARGET_COMPONENT] = "component", - [TARGET_HOST] = "host", -}; - -DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING(target_class, TargetClass); - static const char* const job_type_table[_JOB_TYPE_MAX] = { [JOB_LIST] = "list", [JOB_DESCRIBE] = "describe", diff --git a/test/units/TEST-72-SYSUPDATE.sh b/test/units/TEST-72-SYSUPDATE.sh index 6cabcab5bb..a8bc7432c1 100755 --- a/test/units/TEST-72-SYSUPDATE.sh +++ b/test/units/TEST-72-SYSUPDATE.sh @@ -8,6 +8,7 @@ set -o pipefail SYSUPDATE=/usr/bin/systemd-sysupdate SYSUPDATED=/lib/systemd/systemd-sysupdated UPDATECTL="" +VARLINK_SOCKET=/run/systemd/io.systemd.SysUpdate SECTOR_SIZES=(512 4096) WORKDIR="$(mktemp -d /var/tmp/test-72-XXXXXX)" CONFIGDIR="/run/sysupdate.d" @@ -35,8 +36,8 @@ if [[ ! -e /dev/loop-control ]]; then SECTOR_SIZES=(512) fi -# Set up sysupdated drop-in pointing at the correct definitions and setting -# no verification of images. +# Set up sysupdated and varlink drop-ins pointing at the correct definitions and +# setting no verification of images. mkdir -p /run/systemd/system/systemd-sysupdated.service.d cat >/run/systemd/system/systemd-sysupdated.service.d/override.conf</run/systemd/system/systemd-sysupdate@.service.d/override.conf</dev/null + else + exit 1 + fi +} + +check_new_update_available() { + local client="${1:?}" + + if [[ "$client" == "sysupdate-cli" ]]; then + "$SYSUPDATE" --verify=no check-new + elif [[ "$client" == "varlink" ]]; then + varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.CheckNew '{"target":{"class":"host"}}' | grep available + else + exit 1 + fi +} + update_now() { local update_type="${1:?}" - local checks="${2:-}" + local client="${2:?}" + local checks="${3:-}" # Update to newest version. First there should be an update ready, then we # do the update, and then there should not be any ready anymore @@ -135,7 +170,7 @@ update_now() { # repairing an installation), so that can be overridden via the local. if [[ "$checks" != "no-checks" ]]; then - "$SYSUPDATE" --verify=no check-new + check_new_update_available "$client" fi if [[ "$update_type" == "monolithic" ]]; then @@ -159,7 +194,7 @@ update_now() { fi if [[ "$checks" != "no-checks" ]]; then - (! "$SYSUPDATE" --verify=no check-new) + check_no_new_update_available "$client" fi } @@ -208,6 +243,7 @@ verify_object_fields() { } for sector_size in "${SECTOR_SIZES[@]}"; do +for client in sysupdate-cli varlink; do for update_type in monolithic split-offline split updatectl; do # Disk size of: # - 1MB for GPT @@ -371,18 +407,18 @@ EOF # Install initial version and verify new_version "$sector_size" v1 - update_now "$update_type" + update_now "$update_type" "$client" verify_version_current "$blockdev" "$sector_size" v1 1 # Create second version, update and verify that it is added new_version "$sector_size" v2 - update_now "$update_type" + update_now "$update_type" "$client" verify_version "$blockdev" "$sector_size" v1 1 verify_version_current "$blockdev" "$sector_size" v2 2 # Create third version, update and verify it replaced the first version new_version "$sector_size" v3 - update_now "$update_type" + update_now "$update_type" "$client" verify_version_current "$blockdev" "$sector_size" v3 1 verify_version "$blockdev" "$sector_size" v2 2 test ! -f "$WORKDIR/xbootldr/EFI/Linux/uki_v1+3-0.efi" @@ -394,12 +430,12 @@ EOF new_version "$sector_size" v4 rm "$WORKDIR/source/uki-extra-v4.efi" update_checksums - (! "$SYSUPDATE" --verify=no check-new) + check_no_new_update_available "$client" # Create a fifth version, that's complete on the server side. We should # completely skip the incomplete v4 and install v5 instead. new_version "$sector_size" v5 - update_now "$update_type" + update_now "$update_type" "$client" verify_version "$blockdev" "$sector_size" v3 1 verify_version_current "$blockdev" "$sector_size" v5 2 @@ -409,7 +445,7 @@ EOF # Always do this as a monolithic update for the repair to work. rm -r "$WORKDIR/xbootldr/EFI/Linux/uki_v5.efi.extra.d" "$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null - update_now "monolithic" + update_now "monolithic" "$client" "$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null verify_version "$blockdev" "$sector_size" v3 1 verify_version_current "$blockdev" "$sector_size" v5 2 @@ -421,7 +457,7 @@ EOF mkdir "$CONFIGDIR/optional.feature.d" echo -e "[Feature]\nEnabled=true" > "$CONFIGDIR/optional.feature.d/enable.conf" "$SYSUPDATE" --offline list v5 | grep "incomplete" >/dev/null - update_now "$update_type" + update_now "$update_type" "$client" "$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null verify_version "$blockdev" "$sector_size" v3 1 verify_version_current "$blockdev" "$sector_size" v5 2 @@ -429,7 +465,7 @@ EOF # And now let's disable it and make sure it gets cleaned up rm -r "$CONFIGDIR/optional.feature.d" - (! "$SYSUPDATE" --verify=no check-new) + check_no_new_update_available "$client" "$SYSUPDATE" vacuum "$SYSUPDATE" --offline list v5 | grep -v "incomplete" >/dev/null verify_version "$blockdev" "$sector_size" v3 1 @@ -441,17 +477,17 @@ EOF new_version "$sector_size" v6 if [[ -x "$UPDATECTL" ]]; then systemctl start systemd-sysupdated - "$SYSUPDATE" --verify=no check-new + check_new_update_available "$client" "$UPDATECTL" update |& tee "$WORKDIR"/updatectl-update-6 grep "Done" "$WORKDIR"/updatectl-update-6 (! grep "Already up-to-date" "$WORKDIR"/updatectl-update-6) else # If no updatectl, gracefully fall back to systemd-sysupdate - update_now "$update_type" + update_now "$update_type" "$client" fi # User-facing updatectl returns 0 if there's no updates, so use the low-level # utility to make sure we did upgrade - (! "$SYSUPDATE" --verify=no check-new ) + check_no_new_update_available "$client" verify_version_current "$blockdev" "$sector_size" v6 1 verify_version "$blockdev" "$sector_size" v5 2 @@ -504,7 +540,7 @@ MatchPattern=dir-@v InstancesMax=3 EOF - update_now "$update_type" + update_now "$update_type" "$client" verify_version "$blockdev" "$sector_size" v6 1 verify_version_current "$blockdev" "$sector_size" v7 2 @@ -527,7 +563,7 @@ EOF # (what .transfer files were called before v257) for i in "$CONFIGDIR/"*.conf; do echo mv "$i" "${i%.conf}.transfer"; done new_version "$sector_size" v8 - update_now "$update_type" + update_now "$update_type" "$client" verify_version_current "$blockdev" "$sector_size" v8 1 verify_version "$blockdev" "$sector_size" v7 2 @@ -537,11 +573,11 @@ EOF # Vacuum the partial version, regenerate it on the server, try updating # again and it should succeed. new_version "$sector_size" v9 "corrupt-checksum" - (! update_now "$update_type") + (! update_now "$update_type" "$client") "$SYSUPDATE" --offline list v9 | grep "partial" >/dev/null verify_version_current "$blockdev" "$sector_size" v8 1 # don’t verify the other part of the block device as it’s in an indeterminate state - (! update_now "$update_type" "no-checks") |& tee "$WORKDIR"/update_now-9 + (! update_now "$update_type" "$client" "no-checks") |& tee "$WORKDIR"/update_now-9 cat "$WORKDIR"/update_now-9 grep "is already acquired and partially installed. Vacuum it to try installing again." "$WORKDIR"/update_now-9 "$SYSUPDATE" --offline vacuum |& grep "Removing old partial" >/dev/null @@ -549,15 +585,28 @@ EOF # don’t verify the other part of the block device as it’s in an indeterminate state "$SYSUPDATE" --verify=no list v9 | grep "candidate" >/dev/null new_version "$sector_size" v9 - update_now "$update_type" + update_now "$update_type" "$client" verify_version "$blockdev" "$sector_size" v8 1 verify_version_current "$blockdev" "$sector_size" v9 2 + # Test that checking for an update on a non-existent target fails + # (for backwards compatibility reasons, the validation in sysupdate-cli is + # less strict) + if [[ "$client" == "sysupdate-cli" ]]; then + (! "$SYSUPDATE" --verify=no check-new --component=../) |& grep "Component name invalid" >/dev/null + elif [[ "$client" == "varlink" ]]; then + (! varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.CheckNew '{"target":{"class":"component","name":"../"}}') |& grep org.varlink.service.InvalidParameter >/dev/null + (! varlinkctl call "$VARLINK_SOCKET" io.systemd.SysUpdate.CheckNew '{"target":{"class":"component","name":"doesnotexist"}}') |& grep io.systemd.SysUpdate.NoSuchTarget >/dev/null + else + exit 1 + fi + # Cleanup [[ -b "$blockdev" ]] && losetup --detach "$blockdev" rm "$BACKING_FILE" done done +done # Regression test for https://github.com/systemd/systemd/issues/41501 — check # that a ‘default’ component is only listed by sysupdate if it’s fully configured diff --git a/units/meson.build b/units/meson.build index 83dae605fa..569aa2e2a2 100644 --- a/units/meson.build +++ b/units/meson.build @@ -849,12 +849,23 @@ units = [ 'conditions' : ['ENABLE_SYSUPDATE'], }, { - 'file' : 'systemd-sysupdate.service', + 'file' : 'systemd-sysupdate-update.service', + 'conditions' : ['ENABLE_SYSUPDATE'], + 'symlinks' : ['systemd-sysupdate.service'], # compatibility after rename + }, + { + 'file' : 'systemd-sysupdate@.service', 'conditions' : ['ENABLE_SYSUPDATE'], }, { - 'file' : 'systemd-sysupdate.timer', + 'file' : 'systemd-sysupdate.socket', 'conditions' : ['ENABLE_SYSUPDATE'], + 'symlinks' : ['sockets.target.wants/'], + }, + { + 'file' : 'systemd-sysupdate-update.timer', + 'conditions' : ['ENABLE_SYSUPDATE'], + 'symlinks' : ['systemd-sysupdate.timer'], # compatibility after rename }, { 'file' : 'systemd-sysupdated.service.in', diff --git a/units/systemd-sysupdate.service b/units/systemd-sysupdate-update.service similarity index 86% rename from units/systemd-sysupdate.service rename to units/systemd-sysupdate-update.service index 92ec7266e8..e8a634bfff 100644 --- a/units/systemd-sysupdate.service +++ b/units/systemd-sysupdate-update.service @@ -9,7 +9,7 @@ [Unit] Description=Automatic System Update -Documentation=man:systemd-sysupdate.service(8) +Documentation=man:systemd-sysupdate-update.service(8) Wants=network-online.target After=network-online.target ConditionVirtualization=!container @@ -18,6 +18,8 @@ ConditionVirtualization=!container Type=simple NotifyAccess=main ExecStart=systemd-sysupdate update --cleanup=yes + +# Keep this sandboxing synchronised with systemd-sysupdate@.service CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER CAP_FSETID CAP_MKNOD CAP_SETFCAP CAP_SYS_ADMIN CAP_SETPCAP CAP_DAC_OVERRIDE CAP_LINUX_IMMUTABLE NoNewPrivileges=yes MemoryDenyWriteExecute=yes @@ -31,4 +33,4 @@ SystemCallArchitectures=native LockPersonality=yes [Install] -Also=systemd-sysupdate.timer +Also=systemd-sysupdate-update.timer diff --git a/units/systemd-sysupdate.timer b/units/systemd-sysupdate-update.timer similarity index 94% rename from units/systemd-sysupdate.timer rename to units/systemd-sysupdate-update.timer index b2c7cd4808..d055948c29 100644 --- a/units/systemd-sysupdate.timer +++ b/units/systemd-sysupdate-update.timer @@ -9,7 +9,7 @@ [Unit] Description=Automatic System Update -Documentation=man:systemd-sysupdate.service(8) +Documentation=man:systemd-sysupdate-update.service(8) # For containers we assume that the manager will handle updates. And we likely # can't even access our backing block device anyway. diff --git a/units/systemd-sysupdate.socket b/units/systemd-sysupdate.socket new file mode 100644 index 0000000000..d91ec793e7 --- /dev/null +++ b/units/systemd-sysupdate.socket @@ -0,0 +1,28 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=System Updates +Documentation=man:systemd-sysupdate(8) +DefaultDependencies=no +Before=sockets.target + +[Socket] +ListenStream=/run/systemd/io.systemd.SysUpdate +Symlinks=/run/varlink/registry/io.systemd.SysUpdate +FileDescriptorName=varlink +SocketMode=0666 +Accept=yes +MaxConnectionsPerSource=16 +XAttrEntryPoint=user.varlink=entrypoint +XAttrListen=user.varlink=listen +XAttrAccept=user.varlink=server + +[Install] +WantedBy=sockets.target diff --git a/units/systemd-sysupdate@.service b/units/systemd-sysupdate@.service new file mode 100644 index 0000000000..f8e86dd2c4 --- /dev/null +++ b/units/systemd-sysupdate@.service @@ -0,0 +1,31 @@ +# SPDX-License-Identifier: LGPL-2.1-or-later +# +# This file is part of systemd. +# +# systemd is free software; you can redistribute it and/or modify it +# under the terms of the GNU Lesser General Public License as published by +# the Free Software Foundation; either version 2.1 of the License, or +# (at your option) any later version. + +[Unit] +Description=System Updates +Documentation=man:systemd-sysupdate(8) +DefaultDependencies=no +Conflicts=shutdown.target +Before=shutdown.target + +[Service] +ExecStart=-systemd-sysupdate + +# Keep this sandboxing synchronised with systemd-sysupdate-update.service +CapabilityBoundingSet=CAP_CHOWN CAP_FOWNER CAP_FSETID CAP_MKNOD CAP_SETFCAP CAP_SYS_ADMIN CAP_SETPCAP CAP_DAC_OVERRIDE CAP_LINUX_IMMUTABLE +NoNewPrivileges=yes +MemoryDenyWriteExecute=yes +ProtectHostname=yes +RestrictRealtime=yes +RestrictNamespaces=net +RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6 +SystemCallFilter=@system-service @mount +SystemCallErrorNumber=EPERM +SystemCallArchitectures=native +LockPersonality=yes